ScreenshotNeo

BlogGuides

Web Development with Python: A Practical Guide

Build a Python web app by choosing a framework, shipping one complete feature, and preparing it for production. Includes runnable Django, Flask, and FastAPI examples.

By the ScreenshotNeo team4 October 202612 min read

To build a web application with Python, choose a framework that fits the project, create an isolated environment, implement one complete feature from request to response and persisted data, then deploy with production settings and a suitable application server. Use Django when integrated facilities such as forms and an admin interface fit a conventional web app; Flask when you want a small, flexible foundation and are comfortable choosing extensions; and FastAPI when the central product is an HTTP API with validation and generated OpenAPI documentation.

This guide builds a small JSON API in FastAPI, shows how the same project decision differs in Django and Flask, and covers the steps between a local example and a production deployment. It assumes basic Python; familiarity with HTML and CSS helps if your project renders web pages.

1. What you need before starting

Be comfortable with functions, imports, exceptions, classes or data structures, and reading tracebacks. For a server-rendered site, learn basic HTML and CSS as well. You do not need to learn every framework first: pick a small project and build one vertical slice—input, validation, application logic, response, and data storage—before broadening the scope. A practical learning path typically starts with Python and web foundations and progresses through frameworks, APIs, and deployment. Real Python’s Python web developer learning path lays out that progression.

2. Choose Django, Flask, or FastAPI

Framework Good fit What it gives you What to plan
Django A conventional web application with pages, forms, accounts, and data management. An integrated framework with facilities including forms and an admin interface. Learn its project conventions and deployment path. See Django’s getting-started guide.
Flask A small application or service where you want to choose the structure and components. A flexible foundation; its tutorial builds a blog with registration, login, and editing. Choose and configure any additional components you need, such as database access and form handling. Flask does not impose one project layout or extension set. See the Flask tutorial.
FastAPI An HTTP API with typed request and response models. Request validation, generated interactive API docs, and an OpenAPI schema. API docs help people explore endpoints but do not replace automated tests or broader technical documentation. See FastAPI’s first steps.

There is no universal winner. Compare the shape of your application, the built-in facilities you need, how much structure you want, and the deployment interface and operational needs. A project can also serve HTML and JSON together; choose based on the first feature you need to deliver, then revisit the choice if requirements change.

3. Create a FastAPI project and environment

The following example uses FastAPI because it demonstrates a compact API project with validated JSON input. It uses uv, a workflow documented by FastAPI; a pip-based setup is also possible. Keep the virtual environment local to the project and commit the dependency lockfile so collaborators can reproduce the resolved packages.

mkdir task-api
cd task-api
uv init
uv add fastapi
uv add --dev fastapi[standard]

Replace main.py with this runnable application:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="Task API")

class TaskInput(BaseModel):
    title: str = Field(min_length=1, max_length=200)
    done: bool = False

class Task(TaskInput):
    id: int

_tasks: dict[int, Task] = {}
_next_id = 1

@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}

@app.get("/tasks", response_model=list[Task])
def list_tasks() -> list[Task]:
    return list(_tasks.values())

@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskInput) -> Task:
    global _next_id
    task = Task(id=_next_id, **payload.model_dump())
    _tasks[_next_id] = task
    _next_id += 1
    return task

@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int) -> Task:
    task = _tasks.get(task_id)
    if task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return task

Start the development server with uv run fastapi dev. Visit http://127.0.0.1:8000/docs to explore the generated interactive API documentation. The in-memory dictionary is only for this demonstration: data disappears when the process restarts, and it cannot safely coordinate multiple processes. Replace it with a persistent database before treating this as a real task service.

4. Exercise the API with HTTP requests

With the local server running, create a task, list tasks, and retrieve one by its returned ID:

curl -i -X POST http://127.0.0.1:8000/tasks \
  -H 'Content-Type: application/json' \
  -d '{"title":"Ship the first feature"}'

curl -i http://127.0.0.1:8000/tasks
curl -i http://127.0.0.1:8000/tasks/1

A valid create request returns HTTP 201 and JSON. A title that is missing or outside the declared length limits fails request validation. An unknown task ID returns HTTP 404. These are different outcomes: validation errors mean the supplied request does not match the input model; 404 means the request is valid but the referenced record is absent.

5. Add persistence, validation, and tests

A useful first feature is not complete until its data survives a restart and its important behavior is checked. Pick a database based on the project’s deployment and data needs; this guide does not prescribe a provider. Define a model and schema changes, handle database errors, and avoid sharing a single unsafe in-memory state between workers. Keep request validation at the boundary, and enforce business rules in application logic as well, since not all writes necessarily arrive through the same endpoint.

Test at least the expected path, invalid input, missing records, and persistence behavior. FastAPI’s test client can exercise the app without a live network listener:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_create_and_get_task():
    response = client.post("/tasks", json={"title": "Write tests"})
    assert response.status_code == 201
    task_id = response.json()["id"]

    fetched = client.get(f"/tasks/{task_id}")
    assert fetched.status_code == 200
    assert fetched.json()["title"] == "Write tests"

def test_invalid_task_title():
    response = client.post("/tasks", json={"title": ""})
    assert response.status_code == 422

Install the test dependencies and run your project’s test runner as part of development and deployment workflows. Add tests for whichever database and authentication behavior your application actually uses; a generated API schema does not verify those behaviors.

6. Build a page or API with another framework

Django: start with the integrated application path

Use Django when its conventions and integrated web application facilities match your project. Create a project and app using the official getting-started instructions, define models and forms for your feature, and use the admin where it helps manage application data. Django’s documentation is the authoritative source for the current commands and project structure: Django getting started. Keep views focused on translating HTTP requests into application operations and responses. Add tests for form validation, permissions, and the behavior users depend on.

Flask: choose the pieces around a small core

Use Flask when you want to select your own structure and extensions. Its official tutorial walks through a blog with registration, login, and content editing and is a practical model for organizing a modest application. Decide explicitly how configuration, database access, validation, authentication, and tests fit together; flexibility means these choices belong to the project.

FastAPI: make the API contract explicit

For APIs, define input and output models and status codes, then use the generated schema as a view of the contract. Document authorization, pagination, error formats, and compatibility expectations that the schema alone does not explain. FastAPI provides interactive documentation and OpenAPI generation, described in its first-steps documentation.

7. Capture a web page from Python for previews or reports

Some Python applications need a screenshot of a public web page—for example, a report thumbnail or a preview. A browser automation library gives you control but adds browser installation, resource use, and page-load edge cases. This optional example uses Playwright. Install its Python package and browser using the current Playwright Python installation instructions:

python -m venv .venv
. .venv/bin/activate
python -m pip install playwright
playwright install chromium

Save as capture.py and run with python capture.py https://example.com page.png:

import asyncio
import sys
from playwright.async_api import async_playwright

async def main(url: str, output: str) -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 1440, "height": 900})
        response = await page.goto(url, wait_until="domcontentloaded", timeout=60_000)
        if response is not None and response.status >= 400:
            raise RuntimeError(f"Page returned HTTP {response.status}")
        await page.screenshot(path=output, full_page=True)
        await browser.close()

if __name__ == "__main__":
    if len(sys.argv) != 3:
        raise SystemExit("Usage: python capture.py URL OUTPUT.png")
    asyncio.run(main(sys.argv[1], sys.argv[2]))

For an HTML page your application itself renders, a browser capture can also serve as a visual check. In automated jobs, validate and constrain target URLs: accepting arbitrary URLs from users can expose internal services or local files. Reuse browser processes carefully, set timeouts, close pages and browsers in cleanup paths, and limit concurrency and output size. Pages may continue changing after DOM content loads; choose a readiness condition that fits the site rather than assuming every page is ready at the same event.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API can return a screenshot or PDF without installing and managing a browser. The API accepts screenshot parameters used by other screenshot APIs, which can make switching easier. See the ScreenshotNeo API documentation for options and response details.

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Cookie and consent banners are accepted like a visitor and 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, no card required.

9. Prepare the app for deployment

Local development commands are not production deployment instructions. Django’s deployment guide states: “The runserver command starts a lightweight development server, which is not suitable for production.” The same guide describes WSGI and ASGI interfaces and covers static files, error reporting, and pre-deployment checks. Read Django’s deployment documentation for its checklist.

  • Use a production-capable server and the WSGI or ASGI interface appropriate to the application and deployment.
  • Keep secrets and environment-specific settings outside source code; verify which settings are active in each environment.
  • Configure HTTPS. FastAPI’s deployment guidance explains that TLS is commonly handled by a proxy or cloud service; assign ownership for certificate renewal.
  • Decide how static files and, where applicable, uploaded media are stored and served.
  • Set up process startup, restart behavior, and error reporting; review memory and any required pre-start work.
  • Run framework deployment checks and test the deployed configuration, including database connectivity and critical endpoints.

FastAPI’s deployment concepts cover HTTPS, startup, restarts, replication, memory, and pre-start steps. Host-specific setup and cost vary, so select a provider based on your application’s requirements rather than assuming one deployment fits every project.

10. Troubleshooting common problems

Symptom Likely cause What to check or change
ModuleNotFoundError The package was installed in a different Python environment, or the environment is not active. Activate the project environment and install dependencies through the same interpreter or project tool used to run the app.
Import or startup error after changing code A syntax error, wrong module path, or incompatible dependency can prevent application startup. Read the first traceback that originates in project code; verify the working directory, import path, and locked dependencies.
Connection refused on localhost The server is stopped, listening on a different address or port, or the client URL is wrong. Check the server output and use the exact host and port it reports.
FastAPI returns 422 The JSON does not match the request model or a required field is missing. Check the response detail and send valid JSON with the expected types and constraints.
FastAPI returns 404 for a task The route exists but that record ID is absent, or in this example the process restarted and cleared in-memory data. Verify the ID and use persistent storage for data that must survive restart.
Data disappears or differs across requests State is in process memory, or requests are reaching different worker processes. Move shared durable state into a database or another suitable persistence system.
Browser screenshot is blank or incomplete The page is still rendering, important content is lazy-loaded, navigation failed, or the chosen readiness event is too early. Inspect the navigation response, wait for a relevant selector or explicit condition, and handle timeouts; avoid waiting indefinitely for every network connection to stop.
Browser launch fails in a container The browser binary or required system dependencies may be missing, or the runtime environment restricts launch. Install the browser and dependencies using the automation tool’s current installation guidance, and inspect its launch error.
Works locally but fails in production Environment settings, secrets, HTTPS or proxy configuration, static files, startup behavior, or database connectivity differ. Check deployment logs and configuration, then follow the chosen framework’s production checklist.

11. Performance, reliability, and cost

Do not infer framework performance from the examples: the cited framework documentation provides no comparable benchmark, and real behavior depends on application work, database access, deployment, and traffic. Measure the paths that matter in the target environment before tuning.

For reliability, make failure modes visible: report errors, set sensible timeouts around external work, validate inputs, and test startup and restart behavior. Avoid relying on process-local state when multiple workers or restarts are possible. Use a lockfile or equivalent dependency record so deployments resolve the intended packages; FastAPI’s tutorial documents a uv workflow with a project environment and lockfile, while also describing a pip option.

For cost, compare the actual hosting, database, storage, and operational needs of the application. The research sources do not establish universal provider prices or a best host. A browser capture also consumes resources and needs browser installation and lifecycle management; a screenshot API can reduce that operational work, with its own plan and request costs to evaluate.

12. A practical learning sequence

  1. Refresh Python fundamentals and learn enough HTML/CSS for your app’s interface.
  2. Choose one small project and a framework based on the application shape and facilities it needs.
  3. Create an isolated environment and record dependencies reproducibly.
  4. Build one end-to-end feature, including validation, a response, and persistent data.
  5. Add tests for success, invalid input, missing data, and the important rules of the feature.
  6. Deploy with the framework’s production guidance, HTTPS, static-file handling, error reporting, and a plan for startup and restarts.

If you choose Django and want a structured, project-based next step, Django for Beginners is an optional framework-specific book and course. Check its current edition and listing before buying; it is not a general resource for Flask or FastAPI.

Frequently asked questions

Can I build both a website and an API with Python?

Yes. Choose the framework and project structure around the pages and endpoints you need. You can serve HTML and JSON from one application, or separate them if the project calls for it.

What should I learn before Django?

Learn Python fundamentals and basic HTML/CSS, then follow Django’s getting-started guide while building a small feature. You do not need to master every web framework first.

Does generated API documentation mean my API is fully documented?

No. Generated interactive docs and an OpenAPI schema describe useful parts of the API contract. Explain behavior and operational expectations that the schema cannot convey, and keep tests for behavior.

Can I deploy the in-memory task example as-is?

No. It is for demonstrating routing and validation. Its data vanishes on restart and is not shared safely across multiple worker processes; use persistent storage for a deployed application.