[+FIT2081]: Monash University course, FIT2081 mobile application development.
[+ORM]: Object-Relational Mapping, a technique for mapping object models to relational databases.
[+viewmodel]: In development, ViewModel is a design pattern used to separate the view (UI) from the data model. It is typically used in MVVM (Model-View-ViewModel) architecture, allowing views to automatically update through data binding.
[+axios]: Axios is a Promise-based HTTP client for browsers and Node.js.
[+Vue.js]: Vue.js is a progressive JavaScript framework for building user interfaces.
[+Django]: Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design.
[+Higher-order-function]: A higher-order function accepts another function as input or returns a function as output. In programming, this is the same general move as treating functions as objects we can transform.
This note came from my first Django + Vue.js project. Before that, most of my UI experience came from Kotlin Jetpack Compose in FIT2081[+FIT2081], where ViewModel[+viewmodel] and ORM[+ORM] patterns made state feel close to the interface. A web app forced a different lesson: frontend and backend do not share state directly. They negotiate through HTTP.
The useful lesson was not that HTTP exists. It was that a web UI and a backend do not share state in the way a small mobile app can appear to share state. They meet through an explicit boundary: request, response, status code, headers, body, serialization, and often CORS. This note keeps the Vue.js and Django example because it was the concrete setup where that boundary became visible to me.
Understanding HTTP
HTTP is the basic protocol for moving data on the web. It follows a client-server model: a client, usually a browser, sends requests, and the server returns responses. A page is then assembled from text, styles, images, scripts, and other resources. HTTP can carry complex payloads, but the protocol mainly defines how messages move, not what the data means. That is why an HTTP message has a relatively simple structure.
This article focuses on the common REST-style request/response pattern. HTTP can also support GraphQL, gRPC-web, streaming responses, server-sent events, and other interaction styles. I am keeping the scope narrow because this was the boundary I needed to understand first.
HTTP Request Structure
As shown above, HTTP requests start from the client, so we first explain the client’s request structure.
An HTTP request is a message sent by the client to the server, requesting the server to perform some operation or return some data. It usually contains the following parts:
- Request Line: Includes the HTTP method (e.g., GET, POST, PUT, DELETE), request target (usually a URL), and HTTP version.
Where:
- HTTP Method: Indicates the type of request, such as GET (retrieve resource), POST (submit data), PUT (update resource), or DELETE (delete resource).
- Request Target: Usually the resource path on the server, for example, in a backend running process, there is a URL
/api/users/specifically for getting the user list. - HTTP Version: Such as HTTP/1.1 or HTTP/2.
- Headers: These are key-value pairs that provide additional information about the request, such as content type, authentication tokens, and caching directives.
- Body: This is an optional part that contains data sent to the server, typically used with methods like POST and PUT, such as submitting form data or uploading files. This part often involves conversion of structured data formats like JSON.
HTTP Response Structure
An HTTP response consists of the following parts:
- Status Line: Includes the HTTP version, status code (e.g., 200, 404, 500), and status message.
- Headers: Similar to request headers, these headers provide information about the response.
- Body: Contains data sent back from the server, which can be HTML, JSON, XML, or other formats, but is usually in JSON format.
Common HTTP Status Codes
200 OK: The request has succeeded.201 Created: The request has been fulfilled and has resulted in the creation of one or more new resources.400 Bad Request: The server cannot or will not process the request due to an apparent client error.404 Not Found: The server can’t find the requested resource.500 Internal Server Error: The server has encountered a situation it doesn’t know how to handle.
Setting Up the Backend with Django
Django is a high-level Python web framework. For this note, the backend only needs one small API endpoint that returns a list of users.
The view and URL mapping isolate the backend side of the boundary:
The following code uses Python’s decorator functionality, which is a way to add additional functionality before and after function or method calls. Decorators can make code more concise and readable. For details, you can read Python Decorators. Simply put, decorators achieve functionality extension by passing the decorated function into another function and returning a new function. This is a common usage of higher-order function[+Higher-order-function].
views.py
from django.http import JsonResponse # Import JsonResponse from Django's HTTP modulefrom rest_framework.decorators import api_view # Import api_view decorator from Django REST Framework
@api_view(['GET'])def user_list(request): """ A view that returns a list of users in JSON format. """ # This is just sample data; in actual applications, JSON can be obtained from database queries, which is easy to implement in many databases. users = [ {'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}, ] return JsonResponse(users, safe=False)urls.py
from django.urls import pathfrom .views import user_list
# This is a URL configuration that maps API endpoints to view functions for communication with the frontend.urlpatterns = [ path('api/users/', user_list, name='user-list'),]Setting Up the Frontend with Vue.js
Vue.js is a JavaScript framework for building user interfaces. The frontend side uses axios[+axios] to make a request to the Django endpoint.
The component fetches the JSON payload and renders the user list:
UserList.vue
<template> <div> <h1>User List</h1> <ul> <li v-for="user in users" :key="user.id">{{ user.name }}</li> </ul> </div></template>
<script>import axios from 'axios';
export default { data() { return { users: [], }; }, mounted() { this.fetchUsers(); }, methods: { // Use async functions for handling network communication. If you don't understand the specific principles, you just need to know this is done to ensure correct program execution order and results async fetchUsers() { try { // The await keyword is used to wait for asynchronous operations to complete and can only be used in functions declared as async const response = await axios.get('/api/users/'); this.users = response.data; } catch (error) { console.error('Error fetching users:', error); } }, },};</script>In this example, the fetchUsers method sends a GET request to the /api/users/ endpoint exposed by the backend. When the data is received, it’s stored in the users array and displayed in the template.
In practice, frontend and backend processes often run on different ports, so CORS (Cross-Origin Resource Sharing) needs to be configured to allow cross-domain requests. Django can easily handle this by installing the django-cors-headers package.
Conclusion
This project made me notice a basic boundary that I had previously underweighted.
HTTP communication is not a magic bridge between frontend and backend. It is a set of explicit contracts: methods, status codes, data formats, cross-origin rules, authentication, error handling, and performance constraints. A toy /api/users/ endpoint is enough to see the shape of the problem, but real systems depend on how carefully those contracts are designed.
I now read this as an early note in a longer API-design thread. REST is one style, GraphQL and gRPC are others, and my later AI gateway work is another version of the same question: how do we make a boundary explicit enough that different clients, tools, and services can cooperate without guessing?
Discussion