Web Servers and Web Applications¶
What Is a Web Server?¶
At their core, web servers are software applications designed to monitor incoming network traffic—usually via port 80—by adhering to HTTP standards. While their historical role was primarily delivering fixed assets like HTML files or simple images, modern servers are capable of providing dynamic, personalized content tailored to specific users.
Web applications operate within the environment provided by the web server. The server acts as a gateway, capturing incoming connections and routing them to the appropriate application logic. Consequently, developers focusing on web apps rarely need to modify or implement functionality within the underlying server software itself.
A web application is composed of two distinct parts: client-side and server-side components. The client-side logic runs directly within the user's web browser, whereas the server-side logic is executed on the remote host.
How Requests and Responses Work¶
The process begins when a user triggers an action, such as navigating to a URL or clicking a hyperlink, which prompts the browser to dispatch a request to the server. Upon arrival, the server evaluates the request and generates a suitable response. This output—which might be HTML markup, JSON-formatted data, or a visual asset—is then sent back for the browser to render for the user.
Flow of a Typical Request¶
- The user clicks a link in their browser.
- The browser makes a request to the server.
- The server receives the request and constructs a response.
- The server returns the response.
- The browser processes the response and displays it to the user.
Three Pillars of the Frontend¶
Building out the interface that users interact with in their browser generally involves balancing three distinct yet interconnected technologies:
- HTML serves as the architectural foundation for the page's structure.
- CSS defines the visual aesthetics and layout.
- JavaScript is employed to handle any interactive or dynamic elements.
The Backend Perspective¶
In contrast, constructing the server-side logic—often referred to as the backend—requires a focus on the processes necessary to assemble and deliver a response to the client. This work frequently entails interfacing with external software, such as database management systems, to fetch the required information. Whether the server is running on a developer's local machine or a remote data center, the goal remains the same: efficient data processing and delivery.
!!!+ tip Current industry trends have blurred the lines between frontend and backend. It is increasingly uncommon for a developer to operate in a total silo—such as managing only a database—without also touching the logic that bridges both sides of the application.
Resource Retrieval and Security¶
When a browser initiates a connection to a website, it sends a formal request to the server. The server then compiles the requested content and transmits it back. If the resulting page references external assets like images, stylesheets, or scripts, the browser typically initiates additional, individual requests for each of those items.
Note
The HTTP/2 Server Push model is an exception to this pattern. It allows the server to proactively send assets to the browser before they are explicitly requested, reducing latency.
Security Implications¶
Each resource that the browser retrieves is also a potential security threat. If a malicious user has found a way to include their own JavaScript as part of a site, that script will be executed on every visitor's machine.
Careful!
Injected scripts can capture every keystroke you make—including passwords—and send them to an attacker. You could also be redirected to a convincing replica of a legitimate page and unknowingly hand over sensitive information such as credit card details. Never underestimate the danger of untrusted scripts running in a browser.
Building a Simple Web Application¶
At its core, a web application must create a response to each request. Developers do not typically implement the web-server functionality and the HTTP-protocol specifics, but instead use a framework that abstracts away many of those low-level tasks. In this tutorial, our framework of choice is Django.
Note
If you are coming from the Java ecosystem, the equivalent de facto framework is Spring.
Prerequisites¶
Before getting started, ensure you have the following installed on your machine:
- Python 3.8+ — Django requires a modern version of Python. Verify your installation by running
python --versionin your terminal. - pip — Python's package manager, used to install Django and other dependencies.
- A virtual environment tool — It is strongly recommended to isolate your project dependencies using
venv, which ships with Python's standard library.
Setting Up Your Environment¶
First, create a dedicated directory for your project and navigate into it:
Next, create and activate a virtual environment to keep your dependencies isolated from the rest of your system:
# Create the virtual environment
python -m venv venv
# Activate it (macOS/Linux)
source venv/bin/activate
# Activate it (Windows)
venv\Scripts\activate
With the environment active, install Django via pip:
You can confirm the installation was successful by checking the version:
Tip
If you encounter permission issues during installation, make sure your virtual environment is activated. You should see (venv) at the beginning of your terminal prompt.
Creating a Django Project¶
Django provides a command-line utility that scaffolds the boilerplate structure of a new project for you. Run the following to generate a project named myserver:
Info
The trailing . tells Django to place the project files in the current directory rather than creating an additional nested folder. Forgetting it will result in an extra level of nesting that can be confusing.
Your directory should now look like this:
my_web_server/
├── manage.py
└── myserver/
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
Here is a brief overview of these files:
manage.py— A command-line utility for interacting with your Django project (running the server, applying migrations, etc.).settings.py— The central configuration file for your project (database settings, installed apps, middleware, etc.).urls.py— The top-level URL dispatcher. This is where incoming request paths are mapped to the appropriate logic.wsgi.py/asgi.py— Entry points for WSGI- and ASGI-compatible web servers used in production deployments.
Creating a Django App¶
In Django, a project is the overall container, while an app is a self-contained module responsible for a specific piece of functionality. A single project can host many apps. Create one now called home:
This generates a new home/ directory:
home/
├── __init__.py
├── admin.py
├── apps.py
├── migrations/
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
For this tutorial, the file we care most about is views.py, which is where request-handling logic lives.
You must also register the new app with your project by adding it to the INSTALLED_APPS list in myserver/settings.py:
Danger
Forgetting to register your app in INSTALLED_APPS is one of the most common mistakes for beginners. Django will not recognize your app's models, templates, or other components until it is listed here.
Writing Your First View¶
A view in Django is a Python function (or class) that receives an HTTP request and returns an HTTP response. Open home/views.py and replace its contents with the following:
from django.http import HttpResponse
def index(request):
return HttpResponse("<h1>Hello, World!</h1>")
This is the simplest possible view—it accepts any incoming request and responds with a plain HTML string.
Connecting a URL to Your View¶
For Django to route an incoming request to your view, you need to register a URL pattern. Start by creating a urls.py file inside the home/ app directory:
# home/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
This maps the root path ('') to the index view you just wrote. The optional name parameter provides a convenient identifier you can use to reference this URL pattern elsewhere in your code.
Next, include the app's URLs in the project-level URL configuration. Open myserver/urls.py and update it:
# myserver/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('home.urls')),
]
The include() function delegates any requests matching the given path prefix to the app's own URL configuration.
Running the Development Server¶
Django includes a lightweight development server so you can test your application locally without any additional setup. Start it with:
You should see output similar to the following in your terminal:
Open your browser and navigate to http://127.0.0.1:8000/. You should be greeted with your Hello, World! message.
Warning
The built-in development server is intended for local development only. It is not suitable for production use. For deploying a Django application, consider using a production-grade WSGI server such as Gunicorn paired with a reverse proxy like Nginx.
How It All Fits Together¶
To recap the flow of a request through your new Django application:
- A user navigates to
http://127.0.0.1:8000/in their browser. - Django's development server receives the request and passes it to the URL dispatcher (
myserver/urls.py). - The dispatcher matches the path (
'') and delegates tohome/urls.py. home/urls.pymaps the path to theindexview inhome/views.py.- The
indexfunction constructs anHttpResponsecontaining HTML markup. - Django sends that response back to the browser, which renders the page for the user.
Handling Multiple Routes¶
Web applications typically respond to requests at multiple paths, where each path has specific functionality. Here is an example with three separate routes:
# urls.py
from django.urls import path
from .views import pathView, trailView, routeView
urlpatterns = [
path('path/', pathView, name='path'),
path('trail/', trailView, name='trail'),
path('route/', routeView, name='route'),
]
# views.py
from django.http import HttpResponse
def pathView(request):
return HttpResponse('Path')
def trailView(request):
return HttpResponse('Trail')
def routeView(request):
return HttpResponse('Route')
Passing Data Through URLs¶
Each request may contain information that is being sent to the web application. There are two primary ways to achieve this through the URL itself.
Method 1: Path Parameters¶
You can embed parameters directly into the URL path. For example, visiting http://localhost:8000/greet/ada/ would parse ada as a parameter:
# urls.py
from django.urls import path
from .views import greetView
urlpatterns = [
path('greet/<str:user>/', greetView, name='greet'),
]
# views.py
from django.http import HttpResponse
def greetView(request, user):
return HttpResponse('Hi ' + user)
Method 2: GET Parameters (Query Strings)¶
The more conventional approach uses query strings. The URL would look like http://localhost:8000/greet/?user=ada:
# urls.py
from django.urls import path
from .views import greetView
urlpatterns = [
path('greet/', greetView, name='greet'),
]
# views.py
from django.http import HttpResponse
def greetView(request):
user = request.GET.get('user')
return HttpResponse('Hi ' + user)
!!!+ tip GET parameters are the more widely used convention for passing data in URLs. Path parameters are better suited for identifying resources (e.g., /users/42/), while query strings are ideal for optional filters and search terms (e.g., /search/?q=django).
Rendering Templates¶
The applications we have built so far respond with plain strings. While the underlying mechanism is identical, real-world applications serve HTML content created using templates—files that include embedded commands for injecting dynamic content.
Django ships with its own template engine. Templates for an app called pages should be placed in the src/pages/templates/pages/ folder (or home/templates/home/ following standard conventions).
!!!+ note If you are coming from Java, Thymeleaf serves a similar role in the Spring ecosystem.
A Basic Template Example¶
# urls.py
from django.urls import path
from .views import homePageView
urlpatterns = [
path('', homePageView, name='home'),
]
# views.py
from django.http import HttpResponse
from django.template import loader
def homePageView(request):
template = loader.get_template('pages/index.html')
return HttpResponse(template.render())
<!-- templates/pages/index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
Hello from the template side.
</body>
</html>
!!!+ tip Make sure the template file name and path match exactly what you pass to loader.get_template(). A mismatch is a very common source of TemplateDoesNotExist errors.
Passing Data to Templates¶
The purpose of using templates is to generate content dynamically. The easiest way to pass data is through a context dictionary using the render helper function:
# views.py
from django.shortcuts import render
def homePageView(request):
return render(request, 'pages/index.html', {
'msg': 'Hi!',
'from': 'Ada',
})
The context variables are then rendered using the {{ }} syntax in the template:
<!-- templates/pages/index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
{{ msg }} from {{ from }}
</body>
</html>
Tip
The context variable can contain nested dictionaries and lists, making it flexible enough for complex data structures.
Working with Lists in Templates¶
You can pass lists into your template context and iterate over them using Django's {% for %} tag:
# views.py
from django.shortcuts import render
def homePageView(request):
return render(request, 'pages/index.html', {
'msg': 'Hi!',
'senders': ['Ada', 'Alice', 'Bob'],
})
<!-- templates/pages/index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
{{ msg }} from
{% for user in senders %}
{{ user }}
{% endfor %}
</body>
</html>
Handling Form Submissions¶
Web applications frequently use forms to collect input from users. Forms are defined in HTML using the <form> element, which specifies the target path, the request method, and the input fields.
<form action="/" method="POST">
{% csrf_token %}
<input type="text" name="content"/>
<input type="submit"/>
</form>
Info
The {% csrf_token %} tag is a critical security measure. It generates a hidden input field containing a random token that the server validates on submission. This protects your application against Cross-Site Request Forgery (CSRF) attacks, where a malicious site tricks a user's browser into submitting a form to your server. Never omit this tag from POST forms. We will discuss CSRF in greater detail later in the course.
Sessions and State Management¶
In the examples above, the server needs to maintain state between requests—for example, keeping track of a list that grows as the user adds items. Django handles this through sessions.
Sessions provide a dictionary-like object accessible via request.session that persists data for individual users. The mechanism works as follows:
- The server assigns a unique session ID to each user.
- This ID is stored in a cookie in the user's browser.
- On each subsequent request, the browser sends the cookie back, allowing the server to retrieve the correct session data.
!!!+ note By default, Django stores session data in the database. This means that even if you restart the development server, user sessions remain intact.
Refreshing Your HTML Knowledge¶
If you are new to HTML or need a refresher, the W3Schools HTML Tutorial is an excellent starting point for learning the fundamentals.
Next Steps¶
This tutorial covered the fundamentals of web servers, the request-response cycle, and setting up a Django project. From here, you can explore:
- Templates — Use Django's templating engine to serve dynamic HTML files instead of hardcoded strings.
- Models — Define your data structures and let Django manage the database schema through its ORM.
- Forms — Accept and validate user input on the server side.
- The Django Admin — A powerful, auto-generated interface for managing your application's data.
- Databases and HTTP — The next part of this course will look into using databases and the underlying HTTP protocol in greater depth.
For a deeper dive, the official Django documentation is an excellent and thorough resource.