A RESTful HTTP server built with TypeScript and Express as part of the Boot.dev backend development curriculum.
This is the 3rd project in the 7th course of the Foothill Training plan, covering HTTP server fundamentals through building a Twitter-like microblogging API.
Chirpy is a backend API that powers a microblogging platform. Users can register, post short messages ("chirps"), authenticate using JWTs and refresh tokens, and be upgraded to Chirpy Red membership via webhook events. The project covers core HTTP server concepts — routing, middleware, authentication, authorization, and database integration — without any frontend.
Milestone 1 — HTTP Server Basics
- Express.js server setup with TypeScript and ESM modules
- Static file serving for the admin dashboard
- Readiness check endpoint (
GET /api/healthz) - Admin metrics endpoint to track file server hits
Milestone 2 — Users & Chirps
- PostgreSQL database with Drizzle ORM
- User creation with Argon2 password hashing
- Chirp CRUD (create, read, delete)
- Profanity filtering on chirp content (
kerfuffle,sharbert,fornax→****) - 140-character chirp length limit
Milestone 3 — Authentication
- JWT access tokens (1-hour expiry) with
jsonwebtoken - 60-day refresh tokens stored in the database (cryptographically random hex strings)
- Login endpoint returning both access and refresh tokens
- Token refresh (
POST /api/refresh) and revocation (POST /api/revoke) authMiddlewareusingres.localsfor safe auth data passing across middleware
Milestone 4 — Authorization
- Protected routes for creating/deleting chirps and updating user details
- Ownership enforcement: users can only delete their own chirps
- User profile updates (email and/or password) behind JWT auth
Milestone 5 — Webhooks & Membership
- Polka payment webhook (
POST /api/polka/webhooks) to upgrade users to Chirpy Red - API key authentication for webhook security
- Idempotent webhook handling (
user.upgradedevent) isChirpyRedboolean field on the users table
Milestone 6 — Filtering & Sorting
- Filter chirps by author (
?authorId=<uuid>) - Sort chirps by creation date (
?sort=asc|desc) - DB-level filtering for author, in-memory sort for flexibility
- Node.js 18+
- PostgreSQL database (local or cloud)
- A
.envfile in the project root (see below)
DB_URL=postgres://user:password@localhost:5432/chirpy
JWT_SECRET=your-secret-here
POLKA_KEY=your-polka-api-key
PLATFORM=devnpm installRun database migrations:
npx drizzle-kit generate
npx drizzle-kit migrateStart the server:
npm run devRun tests:
npm test| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/healthz |
None | Server readiness check |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/users |
None | Create a new user |
PUT |
/api/users |
Bearer JWT | Update email and/or password |
POST |
/api/login |
None | Login, returns JWT + refresh token |
POST |
/api/refresh |
Bearer refresh token | Get a new JWT access token |
POST |
/api/revoke |
Bearer refresh token | Revoke a refresh token |
// Request
{ "email": "user@example.com", "password": "hunter2" }
// Response 201
{ "id": "uuid", "email": "user@example.com", "isChirpyRed": false, "createdAt": "...", "updatedAt": "..." }// Request
{ "email": "user@example.com", "password": "hunter2" }
// Response 200
{
"id": "uuid",
"email": "user@example.com",
"isChirpyRed": false,
"token": "<JWT>",
"refreshToken": "<hex string>",
"createdAt": "...",
"updatedAt": "..."
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/chirps |
None | Get all chirps |
GET |
/api/chirps/:id |
None | Get a chirp by ID |
POST |
/api/chirps |
Bearer JWT | Create a new chirp |
DELETE |
/api/chirps/:chirpId |
Bearer JWT | Delete your own chirp |
| Param | Values | Description |
|---|---|---|
authorId |
UUID | Filter chirps by author |
sort |
asc (default) / desc |
Sort by creation date |
// Request
{ "body": "Hello Chirpy world!" }
// Response 201
{ "id": "uuid", "body": "Hello Chirpy world!", "userId": "uuid", "createdAt": "...", "updatedAt": "..." }| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/polka/webhooks |
ApiKey | Handle Polka payment events |
// Request (Authorization: ApiKey <key>)
{
"event": "user.upgraded",
"data": { "userId": "uuid" }
}
// Response 204 (no content)| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/admin/metrics |
None | View file server hit count |
POST |
/admin/reset |
None | Reset hit count (dev only) |
- ESM modules — The project uses native ES modules (
"type": "module") with.jsextensions on all imports. - Drizzle ORM — Type-safe SQL queries with schema-driven migrations. No raw SQL strings.
- Custom error classes —
BadRequestError,UnauthorizedError,ForbiddenError,NotFoundErrormap directly to HTTP status codes via a central error handler. wrapAsyncpattern — All async route handlers are wrapped to forward errors to Express's error middleware, keeping handler code clean and throw-based.res.localsfor auth state — JWT claims are stored inres.locals(notreq.body) so auth data survives across middleware on all HTTP methods, including bodyless ones likeDELETE.
- Add rate limiting to prevent chirp spam
- Paginate
GET /api/chirpsinstead of returning all records - Add integration tests using a test database
- Deploy to a cloud platform (Railway / Fly.io)
- Support image attachments on chirps
- Add a
GET /api/users/:idpublic profile endpoint