Skip to lessons
Bryn Flow
New guide

LEARN SOMETHING. BUILD SOMETHING.

Full-Stack Development

Follow a request from an accessible interface through a server to a database. Build a small task tracker before moving to a larger framework.

6 short lessons · 1 project · video resources · no login for this guide

Start learning All new paths
Browser UI → Server API → SQL database

Before you start: Basic HTML and JavaScript; introductory Python helps.

Your toolkit: HTML · JavaScript · Python · SQL

Your learning path

1Trace a web request

The browser sends an HTTP request to a server. The server checks the request, reads or writes data, and returns a response. A frontend component should not contain a private database password. Draw the boundary before choosing React, Django, or another framework.

Try it: Use your browser’s Network panel to inspect a GET request: method, status, response body, and content type.

2Build an accessible form

Use a real form, explicit labels, a submit button, and visible validation feedback. Handle loading, success, empty, and error states. Native HTML gives keyboard users a usable foundation before JavaScript is added.

Try it: Build a title field that is required and limited to 120 characters. Test submission using only the keyboard.

3Define an API contract

GET /tasks lists tasks; POST /tasks creates one. Validate types and lengths again on the server because client checks can be bypassed. Return suitable status codes and a predictable error body. Check response.ok before treating fetch as a success.

Try it: Write examples of a valid create request and an invalid empty-title request, including the expected responses.

4Store data safely

Use a primary key and parameterized queries. A transaction groups related writes so partial failure does not leave inconsistent data. Add ownership to records before making a multi-user app; knowing a task ID must not grant permission to edit it.

Try it: Create tasks(id, owner_id, title, completed). Verify that a user can only update their own task.

5Test the important boundaries

Test invalid input, failed database operations, and an unauthorized update. Keep user text as text when rendering it; avoid inserting it through innerHTML. A useful integration test sends a request through the actual API and verifies the stored result.

Try it: Attempt an update as a different user and expect a denial even when the record ID is valid.

6Deploy a small complete product

Use environment variables for server credentials, HTTPS, structured error logs, and a database backup plan. Run checks before merging. Keep a rollback path and verify the live app can create, list, and complete a task after deployment.

Try it: Write a README with setup, environment variable names, test commands, and a deployment checklist.

A worked example

Use this small example to connect the lessons to code. Read the comments before running it.

async function loadTasks() {
  const response = await fetch("/api/tasks");
  if (!response.ok) throw new Error("Could not load tasks");
  return response.json();
}
// Render task titles with textContent, not innerHTML.

PUT IT INTO PRACTICE

An accessible task tracker with an API

Build the project described in the lessons, then verify these acceptance criteria:

Deliverable: your source code, a short README, and evidence for each check. Explain one tradeoff you made.

Watch & learn

Harvard CS50: Web Programming Watch JavaScript, Django, SQL, and Testing/CI lessons. The provider’s lectures include video and notes. Open video lessons on the provider’s site ↗

Videos open externally and do not autoplay. Provider exercises, certificates, and account requirements are separate from this free guide.

Check your understanding

Why validate on the server if the form already validates?

Clients can bypass the form entirely. The server must enforce the data contract and authorization.

Keep going

MDN: Learn Web Development — continue with the original documentation and exercises.

Choose your next learning path →