Lightweight Fastify + TypeScript API used in the Rocketseat challenge. It provides a small "courses" API backed by Postgres via Drizzle ORM. The project includes authentication (JWT), password hashing (argon2), tests (Vitest + Supertest), factories (Faker), seeding, and OpenAPI docs in development.
- Node.js (ESM) — project uses a Node 22+ runtime which has native TypeScript support
- TypeScript
- Fastify
- Drizzle ORM (Postgres)
- Zod (validation)
- jsonwebtoken (JWT)
- argon2 (password hashing)
- Vitest + Supertest + Faker for tests
- @scalar/fastify-api-reference / @fastify/swagger for API docs
- Node.js 22.x or newer (or a Node runtime that supports running .ts directly)
- PostgreSQL for development / production
- A
.envfile with at least:
DATABASE_URL=postgresql://user:password@localhost:5432/challenge
JWT_SECRET=your_jwt_secret
NODE_ENV=developmentThere is also a .env.test file configured for tests (points to challenge_test).
- Install dependencies
npm install-
Prepare environment (.env / .env.test)
-
(Optional) run local Postgres via Docker Compose
docker compose up -d- Run migrations
npm run db:migrate- Start server in development
npm run devWhen NODE_ENV=development the OpenAPI docs are available at /docs and the OpenAPI JSON is exposed by Fastify Swagger.
npm run dev— start server in dev mode (usesnode --env-file .env --watch src/server.ts). Node 22+ supports running.tsdirectly so the experimental flag is not required.npm run db:seed— runsrc/db/seed.tsto populate development DB with sample data.npm run db:generate—drizzle-kit generatenpm run db:migrate—drizzle-kit migratenpm run db:studio—drizzle-kit studio(Drizzle Studio)npm run pretest— runs migrations against the test DB using.env.testbefore testsnpm run test— runs the test suite (Vitest). Thetestscript uses.env.testby default.
src/server.ts— server bootstrapsrc/app.ts— Fastify instance, plugins and docs registrationsrc/http/routes/*— route handlers and hookssrc/db/*— Drizzle client, schema and seedsrc/tests/factories/*— test factories (Faker) used by tests
All endpoints are JSON-based and validated using Zod. The server uses Fastify's type provider for Zod, so request bodies/params and responses follow the schemas defined in the route files.
- POST /courses — create a course (title required, validated with Zod)
- GET /courses — list courses
- GET /courses/:id — get course by id
- POST /login — authenticate a user and receive a JWT
See the route files in src/http/routes for full request/response schemas. OpenAPI docs are available at /docs in development.
- JWT: the app issues and verifies JWTs using
jsonwebtokenand expectsJWT_SECRETin the environment. Routes that require authentication use acheckJWTRequesthook. - Password hashing: user passwords are hashed with
argon2(seesrc/db/seed.tsand test factories).
Ensure JWT_SECRET is set in your environment for both development and tests (the test factories will sign tokens during tests).
- Test runner: Vitest
- HTTP assertions: Supertest
- Factories / fake data: @faker-js/faker (factories are in
src/tests/factories) - Coverage: configured with
@vitest/coverage-v8(seecoverage/directory generated by test runs)
Recommended local test flow (uses test DB from .env.test):
# start local Postgres (if needed)
docker compose up -d
# run migrations against test DB (pretest does this automatically when using npm test)
npm run pretest
# run tests (script already uses .env.test)
npm testNote: pretest runs drizzle-kit migrate using .env.test so tests operate against the challenge_test database.
Populate dev DB with sample users and courses:
npm run db:seedSeed uses argon2 for password hashing and Faker for generated data.
docker-compose.ymlis included to run Postgres locally. The includeddocker/setup.sqlcreates thechallenge_testdatabase for tests.- Default connections used by the repo:
- Dev DB:
postgresql://postgres:postgres@localhost:5432/challenge - Test DB:
postgresql://postgres:postgres@localhost:5432/challenge_test
- Dev DB:
Start Postgres:
docker compose up -dRun Drizzle Studio to inspect DB schema and data:
npm run db:studiosequenceDiagram
participant C as Client
participant S as Fastify Server
participant V as Zod Validator
participant DB as Drizzle + PostgreSQL
C->>S: POST /courses {title}
S->>V: Validate body
V-->>S: OK or Error 400
alt valid
S->>DB: INSERT INTO courses (title)
DB-->>S: {id}
S-->>C: 201 {courseId}
else invalid
S-->>C: 400
end
C->>S: GET /courses
S->>DB: SELECT id,title FROM courses
DB-->>S: list
S-->>C: 200 {courses: [...]}
C->>S: GET /courses/:id
S->>V: Validate param id (uuid)
V-->>S: OK or Error 400
alt found
S->>DB: SELECT * FROM courses WHERE id=...
DB-->>S: course
S-->>C: 200 {course}
else not found
S-->>C: 404
end
Note: GitHub and many Markdown renderers support Mermaid diagrams — if the diagram doesn't render in your viewer, open the file in VS Code with the "Markdown Preview Mermaid Support" extension or view it on GitHub.
- The project registers OpenAPI (Fastify Swagger) and an alternative UI (
@scalar/fastify-api-reference) in development. Docs are served at/docswhenNODE_ENV=development.
- The app expects
DATABASE_URLandJWT_SECRETto be set in the target environment. - Long‑running container hosts (Fly, Render, Docker) work well. Serverless platforms (Vercel) require a pooling strategy for Postgres connections.
- Tests writing to the wrong DB: ensure
npm testuses.env.test(this project usesdotenv -e .env.testintestandpretest). - ERR_UNKNOWN_FILE_EXTENSION: using a Node build that supports TypeScript (Node 22+ in this project) removes the need for experimental flags; otherwise compile with
tscor usets-node.
This project is released under the ISC license (see package.json).