Plann.er is a travel management API designed to help users plan trips with friends, organize activities, and keep important links in one place.
This project was developed using a modern and efficient stack focused on performance and strong typing.
The project uses the following technologies:
- Node.js: JavaScript/TypeScript runtime environment.
- TypeScript: Adds static typing to the code, increasing predictability and safety.
- Fastify: Web framework focused on performance and low overhead (v5).
- Prisma: Modern ORM (Object-Relational Mapping) for Node.js and TypeScript.
- PostgreSQL: Production database (via Docker locally, managed service on Vercel/Supabase).
- Zod: Schema declaration and validation library with a focus on TypeScript (v4).
- fastify-type-provider-zod: Integration to ensure Fastify routes are typed and validated via Zod (v7).
- @fastify/cors: CORS support for Fastify.
- Nodemailer: Email sending service for trip and participant confirmation.
- tsx: Fast TypeScript runner (used via
tsx watchduring development).
- Trips: Create, update, and list trip details.
- Participants: Manage invitations and presence confirmation.
- Activities: Schedule activities within a trip's timeframe.
- Links: Store useful links (reservations, maps, etc.) linked to the trip.
- Node.js 20+
- Docker (for local PostgreSQL)
npm installdocker run -d --name planner-db \
-e POSTGRES_USER=planner \
-e POSTGRES_PASSWORD=planner \
-e POSTGRES_DB=planner \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpineCreate a .env file with two database URLs:
# Pooled connection (PgBouncer, port 6543) - used for runtime
DATABASE_URL="postgresql://postgres.gmlipfddxutviyjiinrv:<PASSWORD>@aws-0-us-west-2.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1&sslmode=require"
# Direct connection (port 5432) - used for migrations
DIRECT_URL="postgresql://postgres:<PASSWORD>@db.gmlipfddxutviyjiinrv.supabase.co:5432/postgres?sslmode=require"
API_BASE_URL="http://localhost:3000"
WEB_BASE_URL="http://localhost:3000"
PORT=3000Local development: You can use the direct connection for both by setting
DATABASE_URLto the direct URL.
# For development (creates new migration)
npx prisma migrate dev --name init
# For production/CI (applies pending migrations)
npm run db:migratenpm run devThe API will be available at http://localhost:3000.
npm run buildRuns: prisma generate β tsc (TypeScript compilation)
Note: Migrations are not run during build. They run in the CI pipeline before deployment.
| Script | Description |
|---|---|
npm run dev |
Start dev server with hot reload |
npm run build |
Generate Prisma Client + compile TypeScript |
npm run vercel-build |
Alias for build (used by Vercel) |
npm run db:migrate |
Run prisma migrate deploy |
npm start |
Run production build from dist/ |
Push to main β GitHub Actions β (1) npm ci
β (2) prisma generate
β (3) prisma migrate deploy (via pooled connection)
β (4) npm run build
β (5) Deploy to Vercel
| Secret | Description |
|---|---|
DATABASE_URL |
Pooled connection string (PgBouncer, port 6543) with pgbouncer=true |
DIRECT_URL |
Direct connection string (port 5432) β optional, not used in current CI |
VERCEL_TOKEN |
Vercel access token |
VERCEL_ORG_ID |
Vercel organization/team ID |
VERCEL_PROJECT_ID |
Vercel project ID |
- Timeout: Vercel build max 45 min (Pro) / 5 min (Hobby); migrations can exceed this
- Network: Vercel build IPs are dynamic; Supabase direct connection requires allow-listed IPs
- Reliability: Separate CI step provides better logs, retry capability, and rollback control
- Pooled connection: CI uses the PgBouncer URL (port 6543) which accepts any IP
vercel.json uses vercel-build script and rewrites all requests to the serverless entry point:
{
"buildCommand": "npm run vercel-build",
"outputDirectory": "dist",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": null,
"rewrites": [{ "source": "/(.*)", "destination": "/api/index" }]
}- Push to GitHub (triggers workflow)
- GitHub Actions runs migrations + build + deploys to Vercel
- Vercel runs
npm run vercel-build(compile only) and serves fromdist/
src/
βββ routes/ # Route handlers (Fastify plugins)
βββ lib/ # Shared utilities (prisma, mail, date helpers)
βββ errors/ # Custom error classes
βββ server.ts # App factory (buildApp) + dev entry point
βββ env.ts # Zod-validated environment config
βββ error-handlers.ts# Global error handler
api/
βββ index.ts # Vercel serverless entry point
prisma/
βββ schema.prisma # Database schema (PostgreSQL, uses url + directUrl)
βββ migrations/ # Migration history
.github/workflows/
βββ deploy.yml # CI/CD pipeline
The Prisma schema uses both pooled and direct URLs:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // Pooled (PgBouncer) - runtime
directUrl = env("DIRECT_URL") // Direct - migrations
}| Connection | Port | Use Case | IP Restrictions |
|---|---|---|---|
| Pooled (DATABASE_URL) | 6543 | Runtime (Vercel, CI) | None (works from any IP) |
| Direct (DIRECT_URL) | 5432 | Migrations (local, CI) | Requires allow-list in Supabase |
Developed with π during Rocketseat's NLW