Web programming means building things that run on the internet: the pages you see (frontend), the logic and data behind them (backend), and the glue that holds everything together (APIs, servers, databases). This guide is a practical, step-by-step introduction for beginners who want to actually build and deploy real applications. You’ll learn the core concepts, read and write code, set up Linux servers (Ubuntu), deploy to DigitalOcean, and understand how to freelance as a programmer. Along the way we’ll use HTML, CSS, JS, Python, Django, and ReactJS, discussing real-world decisions in full stack web development.
Plain English: HTTP is the language your browser and web server use to talk. You type a URL and your browser sends an HTTP request; the server replies with an HTTP response (HTML, JSON, images, etc.).
Details: A request has a method (GET, POST, PUT, DELETE), a path (/users), headers (metadata like Authorization: Bearer …), and sometimes a body (JSON data). A response has a status code (200 OK, 404 Not Found), headers (Content-Type), and a body. HTTPS is HTTP over TLS encryption for privacy and integrity.
# Example using curl (a command-line HTTP client):
curl -i https://api.github.com/repos/facebook/react
# Output (trimmed):
HTTP/2 200
content-type: application/json; charset=utf-8
...
{ "id":10270250, "name":"react", ... }
Plain English: DNS is the phone book of the internet. It turns human-friendly names (example.com) into IP addresses (like 93.184.216.34) so your computer knows where to send the HTTP request. A URL specifies the protocol (https), domain (example.com), path (/docs), query (?page=2), and sometimes a fragment (#top).
Details: A typical URL is https://api.example.com/v1/users?page=2. DNS resolves api.example.com; your browser opens a TCP connection (plus TLS for HTTPS), sends an HTTP request, receives a response, then renders it.
Plain English: A server is a computer that’s always on, listening for HTTP requests and returning responses. You can rent one from a cloud provider like DigitalOcean and manage it with Linux (Ubuntu).
Details: Servers run web software (Nginx/Apache as reverse proxies, application servers like Gunicorn/Uvicorn for Python), plus your app code and a database (PostgreSQL, MySQL). A production setup often uses: Nginx → Gunicorn → Django → Database.
Plain English: An API is a way for programs to talk. A web API returns data (usually JSON) over HTTP so your frontend (or another service) can read and write information.
Details: REST APIs use HTTP methods to map to operations on “resources” (e.g., /todos). Example: GET /todos (list), POST /todos (create), GET /todos/1 (read), PUT/PATCH /todos/1 (update), DELETE /todos/1 (delete). Authentication (who you are) and authorization (what you can do) are handled via headers, cookies, tokens, or sessions.
[Browser] --HTTPS GET /todos--> [Nginx on Ubuntu Server]
Nginx forwards to Gunicorn (localhost:8000)
Gunicorn loads Django app
Django view queries PostgreSQL
Django returns JSON response
Gunicorn sends response to Nginx
Nginx adds headers / gzip, returns response
[Browser] renders, or JS updates UI
Plain English: HTML is the structure of a webpage. It defines elements like headings, paragraphs, links, forms, and buttons.
Details: The browser parses HTML into a tree called the DOM (Document Object Model). Semantic HTML (using the right tags for meaning) improves accessibility and SEO.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Todo App</title>
</head>
<body>
<h1>Todos</h1>
<ul id="list"></ul>
<form id="form">
<input id="title" placeholder="New todo" />
<button>Add</button>
</form>
<script src="app.js"></script>
</body>
</html>
Plain English: CSS controls how your HTML looks—colors, sizes, layouts.
Details: Modern CSS uses Flexbox and Grid for layout, variables for theming, and responsive units (rem, vw). Keep a consistent design system with spacing, typography, and color scale.
/* styles.css */
:root { --accent: #2563eb; }
body { font-family: system-ui, sans-serif; margin: 2rem; }
h1 { color: var(--accent); }
#list { padding: 0; list-style: none; }
li { display: flex; justify-content: space-between; padding: .5rem; border-bottom: 1px solid #eee; }
Plain English: The DOM is a live tree of your page’s elements that JavaScript can read and change.
Details: document.getElementById lets you grab an element; element.textContent changes its text; element.addEventListener adds event handlers.
// app.js
const list = document.getElementById('list');
function addItem(text) {
const li = document.createElement('li');
li.textContent = text;
list.appendChild(li);
}
addItem('Learn DOM');
Plain English: JavaScript is the programming language that runs in the browser to make pages interactive. It also runs on servers (Node.js), but here we’ll use it mostly in the browser and a ReactJS example.
Details: JS is single-threaded with an event loop. Asynchronous operations (fetch API calls, timers) use promises/async–await. Modules let you split code into files. Debouncing and throttling are patterns to control how often functions run during rapid events (scroll, keyup).
// Fetch JSON from a backend API
async function loadTodos() {
const res = await fetch('/api/todos');
if (!res.ok) throw new Error('Failed to load');
const todos = await res.json();
console.log(todos);
}
loadTodos().catch(console.error);
Plain English: Debouncing makes a function wait until a user stops triggering it for a short time. Example: only run search after the user stops typing for 300ms.
Details: It prevents wasted work and reduces API calls. Implementation stores a timer; each event clears the previous timer and sets a new one.
function debounce(fn, delay = 300) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
const input = document.querySelector('#search');
input.addEventListener('input', debounce(async (e) => {
const q = e.target.value.trim();
if (!q) return;
const res = await fetch('/api/search?q=' + encodeURIComponent(q));
const data = await res.json();
console.log('Results:', data);
}, 300));
Plain English: ReactJS is a JavaScript library for building user interfaces using components—reusable chunks of UI that respond to data changes.
Details: React manages a virtual DOM; when state changes, React computes what changed and updates the real DOM efficiently. Hooks (useState, useEffect) let you manage state and side effects. In modern stacks, use Vite for fast builds.
// React component example (with Vite)
import { useEffect, useState } from 'react';
export default function TodoApp() {
const [todos, setTodos] = useState([]);
const [title, setTitle] = useState('');
useEffect(() => {
fetch('/api/todos').then(r => r.json()).then(setTodos);
}, []);
async function addTodo(e) {
e.preventDefault();
const res = await fetch('/api/todos', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ title })
});
const t = await res.json();
setTodos(prev => [t, ...prev]);
setTitle('');
}
return (
<div>
<h1>Todos</h1>
<form onSubmit={addTodo}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<button>Add</button>
</form>
<ul>
{todos.map(t => <li key={t.id}>{t.title}</li>)}
</ul>
</div>
);
}
Plain English: The backend is where your app’s logic runs, data is stored, and APIs respond. It’s not visible to the user directly.
Details: Backends validate input, enforce business rules, and talk to databases and external APIs (payment, email). Popular programming languages: Python, JavaScript/TypeScript (Node), Go, Java. Beginners often choose Python with Django for speed and clarity.
Plain English: A database stores your data so it persists across requests and server restarts.
Details: Relational databases (PostgreSQL, MySQL) store data in tables with rows and columns and use SQL. NoSQL (MongoDB, Redis) stores documents or key–values. Use indexes for fast queries and migrations to evolve schema.
-- Example SQL table
CREATE TABLE todo (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Plain English: REST is a style of building APIs around resources and standard HTTP methods.
Details: Use nouns (todos), not verbs (getTodos). Use status codes correctly (201 Created on POST, 204 No Content on successful DELETE). Support pagination and filtering using query parameters (?page=2, ?q=search).
Plain English: Authentication is proving who you are (login). Authorization is what you’re allowed to do (permissions).
Details: For beginners, start with session cookies or token-based auth (JWT). Secure tokens in HTTPS-only cookies when possible, include CSRF protection for state-changing operations in browser-based apps.
Plain English: Python is a programming language known for readability. Django is a high-level Python web framework that helps you build secure backends fast.
Details: Django provides an ORM (database layer), routing, views, templates, forms, admin, and security best practices by default. For APIs, you can use vanilla Django (JsonResponse) or Django REST Framework (DRF) for more features. Here’s a minimal Django JSON API without DRF to learn fundamentals.
# 1) Install and start a project
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install django
django-admin startproject todo_project
cd todo_project
python manage.py startapp api
# 2) todo_project/settings.py
INSTALLED_APPS = [
...,
"api",
]
ALLOWED_HOSTS = ["*"] # restrict in production
# Use SQLite for dev (default)
# 3) api/models.py
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=200)
done = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
# 4) api/views.py
import json
from django.http import JsonResponse, HttpResponseNotAllowed
from django.views.decorators.csrf import csrf_exempt
from .models import Todo
def todos(request):
if request.method == "GET":
data = list(Todo.objects.order_by("-id").values("id", "title", "done"))
return JsonResponse(data, safe=False)
elif request.method == "POST":
body = json.loads(request.body or "{}")
t = Todo.objects.create(title=body.get("title", "Untitled"))
return JsonResponse({"id": t.id, "title": t.title, "done": t.done}, status=201)
return HttpResponseNotAllowed(["GET", "POST"])
@csrf_exempt # For demo; in prod, configure proper CSRF handling
def todo_detail(request, pk):
try:
t = Todo.objects.get(pk=pk)
except Todo.DoesNotExist:
return JsonResponse({"detail":"Not found"}, status=404)
if request.method == "PATCH":
body = json.loads(request.body or "{}")
if "title" in body: t.title = body["title"]
if "done" in body: t.done = bool(body["done"])
t.save()
return JsonResponse({"id": t.id, "title": t.title, "done": t.done})
elif request.method == "DELETE":
t.delete()
return JsonResponse({}, status=204, safe=False)
return HttpResponseNotAllowed(["PATCH", "DELETE"])
# 5) api/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("todos", views.todos),
path("todos/<int:pk>", views.todo_detail),
]
# 6) todo_project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("api.urls")),
]
# 7) Run migrations and server
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
Plain English: Git is a tool to track changes in code; GitHub hosts repositories online so you can collaborate and deploy.
Details: Learn commits, branches, merges, pull requests. Commit small, meaningful changes with clear messages. Use .gitignore to avoid committing secrets and build artifacts.
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin git@github.com:you/todo.git
git push -u origin main
Plain English: Package managers install libraries other people wrote so you don’t reinvent the wheel. npm handles JavaScript packages; pip handles Python packages.
Details: Use package.json for frontend dependencies, requirements.txt or pip-tools/poetry for Python. Lock versions for reproducible builds.
# JavaScript
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
# Python
python -m venv .venv
source .venv/bin/activate
pip install django gunicorn
pip freeze > requirements.txt
Plain English: A virtual environment isolates a project’s Python packages so they don’t conflict with other projects.
Details: Activate per project; install packages; deactivate when done. For Node, project-level node_modules already isolate dependencies.
Plain English: Testing is writing code that checks your code works. It prevents regressions as you add features.
Details: Start with unit tests for functions and API tests for endpoints. Django includes a test runner and a client to call views.
# api/tests.py
from django.test import TestCase, Client
class TodoTests(TestCase):
def test_create_and_list(self):
c = Client()
r = c.post("/api/todos", data='{"title":"T"}', content_type="application/json")
self.assertEqual(r.status_code, 201)
r = c.get("/api/todos")
self.assertIn("T", r.content.decode())
Plain English: Linux is an operating system used on servers. Ubuntu is a popular Linux distribution that’s beginner-friendly.
Details: You connect via SSH, install software with apt, manage services with systemd, configure firewalls with ufw, and place your code on the server. Most production servers run Linux.
Plain English: DigitalOcean sells virtual servers called Droplets. You can spin up an Ubuntu Droplet in minutes to host your app.
Details: Choose a region near users, a plan (start with 1–2GB RAM), add SSH keys for login, and enable backups. Point your domain’s DNS A record to the Droplet’s IP.
# 1) SSH to your Droplet (replace IP)
ssh root@YOUR_SERVER_IP
# 2) Create a non-root user
adduser app
usermod -aG sudo app
rsync -av ~/.ssh /home/app/
chown -R app:app /home/app/.ssh
# 3) Basic hardening and updates
apt update && apt upgrade -y
ufw allow OpenSSH
ufw allow http
ufw allow https
ufw enable
# 4) Install system packages
apt install -y python3-pip python3-venv git nginx
# 5) Get your code onto the server
su - app
git clone https://github.com/you/todo.git
cd todo
# 6) Python virtualenv + deps
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Apply migrations and collect static if needed
python manage.py migrate
python manage.py collectstatic --noinput
# 7) Gunicorn systemd service
sudo tee /etc/systemd/system/todo.service >/dev/null <<'EOF'
[Unit]
Description=Gunicorn for Django todo
After=network.target
[Service]
User=app
Group=www-data
WorkingDirectory=/home/app/todo
Environment="PATH=/home/app/todo/.venv/bin"
ExecStart=/home/app/todo/.venv/bin/gunicorn --workers 3 --bind unix:/home/app/todo/todo.sock todo_project.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now todo
sudo systemctl status todo
# 8) Nginx server block
sudo tee /etc/nginx/sites-available/todo >/dev/null <<'EOF'
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /home/app/todo/static/;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/app/todo/todo.sock;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/todo /etc/nginx/sites-enabled/todo
sudo nginx -t
sudo systemctl restart nginx
# 9) DNS: Point yourdomain.com A record to YOUR_SERVER_IP
# 10) HTTPS with Let's Encrypt (Certbot)
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
sudo systemctl status nginx
Result: Nginx terminates HTTPS, serves static files, and reverse-proxies dynamic requests to Gunicorn, which runs Django. This is a standard production architecture on Ubuntu/Linux servers.
Plain English: Build your React app into static files and let Nginx serve them.
Details: Configure the frontend to call /api/* (same domain) to avoid CORS in production. Use a separate Nginx location block for the React build.
# On your local machine
cd my-react-app
npm run build
# Upload the dist/ folder to the server (e.g., /home/app/react-dist)
rsync -av dist/ app@YOUR_SERVER_IP:/home/app/react-dist/
# Nginx server block addition:
location / {
root /home/app/react-dist;
try_files $uri /index.html;
}
location /api/ {
include proxy_params;
proxy_pass http://unix:/home/app/todo/todo.sock;
}
Endpoints implemented above:
Test with curl:
curl -X POST http://localhost:8000/api/todos \
-H 'Content-Type: application/json' \
-d '{"title":"Ship MVP"}'
curl http://localhost:8000/api/todos
// In a Vite React app
import { useEffect, useState } from 'react';
function TodoItem({ t, onToggle, onDelete }) {
return (
<li>
<label>
<input type="checkbox" checked={t.done} onChange={() => onToggle(t)} />
{t.title}
</label>
<button onClick={() => onDelete(t.id)}>Delete</button>
</li>
);
}
export default function App() {
const [todos, setTodos] = useState([]);
const [title, setTitle] = useState('');
async function load() {
const r = await fetch('/api/todos');
setTodos(await r.json());
}
useEffect(() => { load(); }, []);
async function add(e) {
e.preventDefault();
const r = await fetch('/api/todos', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ title })
});
setTitle('');
const t = await r.json();
setTodos([t, ...todos]);
}
async function toggle(t) {
const r = await fetch('/api/todos/' + t.id, {
method: 'PATCH',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ done: !t.done })
});
const updated = await r.json();
setTodos(todos.map(x => x.id === t.id ? updated : x));
}
async function remove(id) {
await fetch('/api/todos/' + id, { method: 'DELETE' });
setTodos(todos.filter(x => x.id !== id));
}
return (
<div>
<h1>Todos</h1>
<form onSubmit={add}>
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="New todo" />
<button>Add</button>
</form>
<ul>
{todos.map(t => (
<TodoItem key={t.id} t={t} onToggle={toggle} onDelete={remove} />
))}
</ul>
</div>
);
}
Plain English: CORS controls which origins (domains/ports) can call your API from the browser. During local development, your React dev server and Django dev server run on different ports; you’ll need to allow cross-origin requests.
Details: Install django-cors-headers and allow http://localhost:5173 (Vite default) during development. In production, avoid CORS by serving React and API from the same domain.
pip install django-cors-headers
# settings.py
INSTALLED_APPS += ["corsheaders"]
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware"] + MIDDLEWARE
CORS_ALLOWED_ORIGINS = ["http://localhost:5173"]
# Nginx gzip example
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# .env example (do not commit)
DJANGO_SECRET_KEY="your-secret"
DATABASE_URL="postgres://user:pass@localhost:5432/todo"
A tech stack is the set of tools you use: frontend, backend, database, hosting, and CI/CD. For beginners focused on full stack web development:
Knowing a few Linux commands makes deployment and troubleshooting straightforward.
# Files and directories
ls -la # list all files
cd /var/www # change directory
cp, mv, rm -rf # copy, move, remove (careful with rm -rf)
# Processes and services
ps aux | grep gunicorn
sudo systemctl status nginx
sudo journalctl -u todo -f # follow logs for your service
# Networking
ss -tulpn | grep LISTEN # show listening ports
curl -I https://yourdomain.com # check headers
# Permissions
chown -R app:www-data /home/app/todo
chmod 640 file
// Example error shape
{
"error": "VALIDATION_ERROR",
"message": "title is required",
"fields": { "title": "This field is required." }
}
// Loading and error states example
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function load() {
try {
setLoading(true); setError(null);
const r = await fetch('/api/todos');
if (!r.ok) throw new Error('Network error');
setTodos(await r.json());
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
}
Freelancing is offering your development services directly to clients. Start with small, well-defined web development projects and build a portfolio. Here’s a step-by-step process focused on execution rather than hype.
# Example skeleton SOW (Statement of Work)
Project: Local Bakery Website
Scope:
- Frontend: React SPA with Home, Menu, Orders.
- Backend: Django API with Orders CRUD, email notifications.
- Hosting: DigitalOcean Droplet (Ubuntu), Nginx, HTTPS.
Deliverables:
- Source code on GitHub
- Admin access and documentation
Timeline: 3 weeks
Price: $X (50% upfront, 30% mid, 20% final)
Out of Scope: Payment gateway, POS integration
Maintenance: $Y/month for hosting, updates, security
This approach turns “how to freelance as a programmer” into repeatable steps using a stable, well-understood stack. It reliably covers frontend, backend, APIs, servers, and DevOps basics on Linux/Ubuntu.
The following resources are consistently useful for beginners learning web development, frontend, backend, APIs, servers, and full stack web development. They complement this guide with tutorials, references, and deep dives into programming languages and frameworks.
These are among the best resources for self-taught programming, with practical examples and explanations aligned to real-world work and freelancing needs.
Scenario: A local gym has a brochure site on a slow shared host, no SSL, and no CMS. Goal: Faster site, class schedule management, lead capture form, and admin dashboard.
# Create a new Django app and superuser
python manage.py startapp blog
python manage.py createsuperuser
# PostgreSQL basics (on Ubuntu)
sudo apt install -y postgresql postgresql-contrib
sudo -u postgres createuser -P todo_user
sudo -u postgres createdb -O todo_user todo_db
# Django DATABASES setting (using psycopg)
pip install psycopg2-binary
# settings.py (example)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "todo_db",
"USER": "todo_user",
"PASSWORD": "strongpassword",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
# Nginx reload without downtime
sudo nginx -t && sudo systemctl reload nginx
# System updates and reboots
sudo apt update && sudo apt upgrade -y
sudo reboot
You learned how the web works (HTTP, DNS, APIs), built a frontend with HTML/CSS/JS and ReactJS, created a backend with Python and Django, designed REST endpoints, handled CORS, and deployed to a Linux Ubuntu server on DigitalOcean using Nginx, Gunicorn, and HTTPS. You also saw performance and security basics, practical debugging, and how to freelance as a programmer using a repeatable full stack. This is the foundation of professional web development.
Next steps: add authentication (Django sessions or JWT), switch to PostgreSQL in production, write automated tests for critical flows, set up CI/CD (GitHub Actions), add monitoring (uptime checks, logs), and expand your portfolio with two more small, real projects. With this process and these resources, you can confidently build, deploy, and maintain modern web applications end-to-end.
