2. Our plan

By the end of the chapter you'll have built an API that serves aggregated news locally and deployed its authenticated HTTP surface as a hosted tutorial app. The path there is eight sections long, and each one exists to solve a specific problem the previous setup couldn't handle. This page is the map.

What you'll build

The chapter assembles a FastAPI service one layer at a time:

Files and features
  • api_spec.md: sketch the API surface on paper before writing any route code.
  • main.py: the FastAPI app, with routes, Pydantic request and response models, and dependency wiring.
  • database.py: connect to PostgreSQL through SQLAlchemy and give each request its own session.
  • init_db.py: create the database tables.
  • sources.py: fan out to NewsAPI and the Guardian and normalise their responses into one schema.
  • cache.py: cache aggregated results in the database so the upstreams aren't hit on every call.
  • auth.py: generate, hash, and verify API keys.
  • rate_limit.py: throttle callers so one client can't take the service down.
  • tests/: exercise every endpoint with pytest and FastAPI's TestClient, mocking the upstream calls.
  • requirements.txt and .env: dependencies and secrets for running and deploying.

Where Chapter 11 left off

Chapter 11 built a Python script that pulled articles from NewsAPI and the Guardian and merged them into a single feed. That script is fine for a job that runs once. It dies the moment the function returns. Nobody else can call it. Nobody can break it. Nobody even knows it exists.

This chapter takes the same logic and turns it into an HTTP service. You exercise the live news integrations locally, then deploy the service shell to Railway so you can verify hosting, persistence, authentication, and documentation without assuming your free upstream credentials are licensed for a public deployment. That sounds like a small change. It isn't.

What changes when an API goes public

A script trusts whoever runs it. A service can't trust anyone. The moment your URL is reachable from outside your laptop, you face problems your script never had to think about:

  • Requests show up that you didn't expect. Wrong methods, missing parameters, garbage JSON, brackets where numbers should be. The script never had to defend against any of this because the only caller was you. The service has no idea who's on the other end.
  • The process can't be where the data lives. Your script held its results in memory. The service will restart on deploys and after crashes; when it does, in-memory state is gone. Real services keep their data in a database that outlives any one process.
  • Anyone can call it, so you need to know who is. Without authentication, your URL is public and free. Someone who embeds it in a popular app leaves you paying NewsAPI's bills for their traffic. Identifying callers, even loosely with API keys, is what lets you treat regular users differently from abusers.
  • One bad caller can take everyone down. A poorly-written client looping at a thousand requests per second will saturate your database connections long before it saturates your CPU. Without rate limits, one caller is enough to make the API unusable for everyone else.
  • The thing has to keep working through changes. Once the service is live you can't experimentally break it. Tests are the contract that tells you immediately whether a change broke something a real caller depended on.
  • It has to run somewhere other than your laptop. A service on localhost is a local build. A service on a real host with a real URL is a deployment, even if its upstream data providers still require a separate licensing decision.

The rest of the chapter is the answer to each of those problems. Each problem turns into a specific property the finished API will carry.

The order we'll work in

Section 3 introduces FastAPI. You install the framework, sketch the API surface on paper, and stand up a tiny app with two endpoints that returns JSON. The auto-generated /docs page comes along for free. By the end of section 3, your service is running on localhost but doesn't yet take any input.

Section 4 teaches the endpoints to take input. Path parameters, query parameters, and Pydantic models for request bodies and responses. Type hints become the validation layer: wrong types get a 422 before the route's code runs, and the same models populate the /docs schema. By the end of this section, the API has a shape clients can actually call.

Section 5 adds persistence. The moment the service needs to remember anything between requests, in-memory state stops working. This section wires PostgreSQL in through SQLAlchemy and introduces FastAPI's dependency-injection pattern, which gives each request its own database session and cleans up afterwards without ceremony. Once persistence is in place, data outlives the process.

Section 6 adds authentication. Now that requests can be served and data can be stored, the API needs to know who's calling. API keys are generated securely, stored hashed, and verified by a dependency that endpoints opt into. Authentication isn't really its own feature -- it's the precondition for every feature that follows. Rate limiting needs to know whose request this is. So does usage tracking, paid tiers, or anything else that distinguishes one caller from another.

Section 7 adds rate limiting. This is the last piece of the platform. A simple in-memory counter does the job for development; the deploy section returns to the limiter once it matters in production, because a single-process counter resets on restart and a multi-worker deploy lets each worker enforce its own private allowance. By the end of this section, everything underneath the aggregator is in place: HTTP, persistence, identity, throttling.

Section 8 is where the aggregator finally arrives. The platform is solid, so we can put the interesting logic on top of it: fan out to NewsAPI and the Guardian, normalise their wildly different response shapes into one schema, cache results in the database so we're not hammering the upstreams on every call, and degrade gracefully when a source is down. This is the chapter's centrepiece, and it's tractable precisely because the five preceding sections did the unglamorous setup work first.

Section 9 adds tests. pytest with FastAPI's TestClient lets you exercise every endpoint without spinning up a server, and mocking the external NewsAPI and Guardian calls keeps tests fast and offline. The point isn't to write tests because writing tests is virtuous. It's so you can change the code tomorrow and know within a second whether you broke anything that mattered.

Section 10 deploys the tutorial service. Uvicorn serves the app, Railway hosts its database-backed HTTP surface, and a quiz at the end checks that the moving parts landed. The page also draws the boundary between local development keys and credentials licensed for a hosted data service.

Before you turn the page

A few things are worth getting in place now, so that nothing later in the chapter blocks on setup:

  • Python 3.10 or newer. FastAPI and Pydantic lean on modern type-hint syntax that older versions don't recognise.
  • PostgreSQL running locally. Section 5 assumes you can connect to a local Postgres instance. If you don't have one already, Docker is the path of least resistance.
  • Local-development upstream keys. Section 8 uses NewsAPI and Guardian credentials while you run the aggregator locally. Check each provider's current terms before signing up or deploying credentials; do not assume a free development key is permitted in a hosted application.
  • Basic comfort with the terminal and SQL. You don't need to be fluent in either. You need to not be scared of them.

If any of those are missing, sort them out before turning the page. When you're ready, section 3 starts with the framework.