A modern Fastify-based REST API that provides information about trade schools across the United States. Built with PostgreSQL, Prisma ORM, and Docker.
- ✅ Fast and lightweight using Fastify framework
- ✅ PostgreSQL database with Prisma ORM
- ✅ Docker containerized database
- ✅ CORS enabled for cross-origin requests
- ✅ Full CRUD operations
- ✅ Query filtering by program and location
- ✅ RESTful API design
- ✅ Health check endpoint
- ✅ Database migrations and seeding
- Framework: Fastify
- Database: PostgreSQL 16
- ORM: Prisma
- Container: Docker & Docker Compose
- Runtime: Node.js (ES Modules)
- Architecture: Service Layer Pattern
- Node.js 18+
- Docker and Docker Compose
- npm or yarn
-
Clone the repository (or you're already here!)
-
Install dependencies:
npm install- Create environment file:
# Create .env file in the root directory
cat > .env << 'EOF'
DATABASE_URL="postgresql://tradeuser:tradepass@localhost:5432/tradeschool?schema=public"
PORT=3000
HOST=0.0.0.0
EOF- Start PostgreSQL database:
npm run docker:upThis will start a PostgreSQL container in the background.
- Run database migrations:
npm run prisma:generate
npm run prisma:migrate- Seed the database:
npm run db:seedOr run migration and seed in one command:
npm run db:setupStart everything with one command:
./start-server.shThis script will:
- ✅ Check if Docker is running
- ✅ Install dependencies (if needed)
- ✅ Start PostgreSQL container
- ✅ Wait for database to be ready
- ✅ Run migrations (first time only)
- ✅ Seed database (first time only)
- ✅ Start the Fastify server
Stop the server:
# Press Ctrl+C to stop Fastify, then run:
./stop-server.shDevelopment Mode (with auto-restart)
npm run devProduction Mode
npm startThe server will start on http://localhost:3000 by default.
# Start PostgreSQL container
npm run docker:up
# Stop PostgreSQL container
npm run docker:down
# View database with Prisma Studio
npm run prisma:studioGET /
Returns API metadata and available endpoints.
Example:
curl http://localhost:3000/Response:
{
"message": "Trade School API",
"version": "2.0.0",
"database": "PostgreSQL with Prisma",
"endpoints": {
"schools": "/api/schools",
"schoolById": "/api/schools/:id",
"programs": "/api/programs"
}
}GET /api/schools
Query Parameters:
program(optional): Filter schools by exact program matchlocation(optional): Filter schools by location (case-insensitive, partial match)
Examples:
# Get all schools
curl http://localhost:3000/api/schools
# Filter by program (exact match)
curl "http://localhost:3000/api/schools?program=Welding"
# Filter by location
curl "http://localhost:3000/api/schools?location=virginia"Response:
{
"count": 8,
"schools": [
{
"id": 1,
"name": "Lincoln Tech",
"location": "Multiple Locations",
"programs": ["Automotive Technology", "HVAC", "Electrical Technology", "Welding"],
"website": "https://www.lincolntech.edu",
"accredited": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
]
}GET /api/schools/:id
Example:
curl http://localhost:3000/api/schools/1Response:
{
"id": 1,
"name": "Lincoln Tech",
"location": "Multiple Locations",
"programs": ["Automotive Technology", "HVAC", "Electrical Technology", "Welding"],
"website": "https://www.lincolntech.edu",
"accredited": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}GET /api/programs
Returns a unique list of all programs offered across all trade schools.
Example:
curl http://localhost:3000/api/programsResponse:
{
"count": 15,
"programs": [
"Automotive",
"Automotive Technology",
"CAD",
"CDL Training",
"CNC Machining",
...
]
}GET /api/stats
Returns statistics about trade schools and programs.
Example:
curl http://localhost:3000/api/statsResponse:
{
"totalSchools": 8,
"accreditedSchools": 8,
"totalPrograms": 15
}POST /api/schools
Request Body:
{
"name": "ABC Technical Institute",
"location": "New York, NY",
"programs": ["Plumbing", "HVAC", "Electrical"],
"website": "https://www.abctech.edu",
"accredited": true
}Example:
curl -X POST http://localhost:3000/api/schools \
-H "Content-Type: application/json" \
-d '{
"name": "ABC Technical Institute",
"location": "New York, NY",
"programs": ["Plumbing", "HVAC", "Electrical"],
"website": "https://www.abctech.edu",
"accredited": true
}'PUT /api/schools/:id
Request Body: (all fields optional)
{
"name": "Updated Name",
"location": "Updated Location",
"programs": ["Updated Programs"],
"website": "https://updated.edu",
"accredited": false
}Example:
curl -X PUT http://localhost:3000/api/schools/1 \
-H "Content-Type: application/json" \
-d '{
"location": "Updated Location"
}'DELETE /api/schools/:id
Example:
curl -X DELETE http://localhost:3000/api/schools/1GET /health
Check API and database connectivity.
Example:
curl http://localhost:3000/healthResponse:
{
"status": "healthy",
"database": "connected",
"timestamp": "2024-01-01T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
| id | Int | Auto-incrementing primary key |
| name | String | School name |
| location | String | School location(s) |
| programs | String[] | Array of programs offered |
| website | String | School website URL |
| accredited | Boolean | Accreditation status (default: true) |
| createdAt | DateTime | Creation timestamp |
| updatedAt | DateTime | Last update timestamp |
# Generate Prisma Client
npm run prisma:generate
# Create and run migrations
npm run prisma:migrate
# Open Prisma Studio (database GUI)
npm run prisma:studio
# Seed database
npm run db:seed
# Run migrations + seed
npm run db:setupCreate a .env file in the root directory:
DATABASE_URL="postgresql://tradeuser:tradepass@localhost:5432/tradeschool?schema=public"
PORT=3000
HOST=0.0.0.0trade-school/
├── routes/
│ └── tradeSchool.routes.js # Route definitions (grouped by feature)
├── controllers/
│ └── tradeSchoolController.js # HTTP request/response handlers
├── services/
│ └── tradeSchoolService.js # Business logic & database operations
├── schemas/
│ └── tradeSchool.schema.js # JSON schemas for validation
├── lib/
│ └── prisma.js # Prisma client singleton
├── prisma/
│ ├── schema.prisma # Database schema
│ └── seed.js # Database seed script
├── index.js # Fastify server & plugin registration
├── package.json # Dependencies and scripts
├── docker-compose.yml # Docker configuration
├── start-server.sh # Start script (recommended)
├── stop-server.sh # Stop script
├── setup.sh # First-time setup script
├── Trade-School-API.postman_collection.json # Postman collection
├── POSTMAN_GUIDE.md # Postman usage guide
├── .env # Environment variables
├── .env.example # Environment template
├── .gitignore # Git ignore rules
└── README.md # This file
The database comes pre-seeded with 8 trade schools:
- Lincoln Tech
- Universal Technical Institute
- Mike Rowe WORKS Foundation
- Tulsa Welding School
- Advanced Technology Institute
- Midwest Technical Institute
- Porter and Chester Institute
- New England Tractor Trailer Training School
This project follows MVC (Model-View-Controller) and Clean Architecture principles with clear separation of concerns:
- Route Modules: Organized by feature/domain (e.g., tradeSchool.routes.js)
- Registered as Fastify plugins for modularity
- Each route file groups related endpoints together
- Maps URLs to controllers + schemas
- Keeps
index.jsclean and minimal - Easy to add new features without bloating main file
- TradeSchoolController: Handles HTTP request/response logic
- Extracts data from requests (params, query, body)
- Calls service methods for business logic
- Maps service results to HTTP responses
- Sets appropriate status codes (200, 201, 404, 503)
- No business logic - pure HTTP concerns
- TradeSchoolService: Contains all business logic and database operations
- Pure functions with no HTTP knowledge
- Handles data transformation and validation
- Centralizes database access
- Reusable across different controllers or contexts
- Easy to unit test
- JSON Schema Validation: Declarative request/response validation
- Fastify automatically validates before reaching controllers
- Type coercion and sanitization
- Self-documenting (can generate OpenAPI/Swagger)
- Performance optimized (compiled at startup)
- Minimal bootstrap file: Just registers plugins and starts server
- No route definitions - all extracted to route modules
- Global middleware and configuration
- Clean entry point
- Prisma Client Singleton: Single database connection
- Prisma Schema: Database model definitions
- Type-safe database queries
- Migration management
- ✅ Separation of Concerns: Each layer has a single, clear responsibility
- ✅ Scalability: Add new features by creating new route modules
- ✅ Testability: Controllers, services, and schemas can be tested independently
- ✅ Maintainability: Easy to find and modify code - routes grouped by feature
- ✅ Plugin System: Fastify plugins enable encapsulation and reusability
- ✅ Reusability: Services can be used by multiple controllers
- ✅ Type Safety: Schemas + Prisma provide end-to-end type safety
- ✅ Clean Code: Thin layers with focused responsibilities
HTTP Request
↓
Route Plugin (routes/tradeSchool.routes.js) → Matches URL
↓
Schema Validation (schemas/) → Validates request
↓
Controller Method (controllers/) → Handles HTTP concerns
↓
Service Method (services/) → Business logic & database
↓
Prisma (lib/prisma.js) → Database query
↓
PostgreSQL Database
The API uses JSON Schema for automatic validation. Here are some examples:
curl -X POST http://localhost:3000/api/schools \
-H "Content-Type: application/json" \
-d '{
"name": "ABC Tech",
"location": "New York, NY",
"programs": ["Plumbing", "HVAC"],
"website": "https://www.abctech.edu"
}'curl -X POST http://localhost:3000/api/schools \
-H "Content-Type: application/json" \
-d '{
"name": "ABC Tech",
"location": "New York, NY"
}'Error Response:
{
"statusCode": 400,
"error": "Bad Request",
"message": "body must have required property 'programs'"
}curl -X POST http://localhost:3000/api/schools \
-H "Content-Type: application/json" \
-d '{
"name": "ABC Tech",
"location": "New York, NY",
"programs": "Plumbing",
"website": "https://www.abctech.edu"
}'Error Response:
{
"statusCode": 400,
"error": "Bad Request",
"message": "body/programs must be array"
}A complete Postman collection is included for testing all API endpoints!
- Open Postman
- Click Import → File
- Select
Trade-School-API.postman_collection.json - Start testing!
- ✅ All 13 endpoints pre-configured
- ✅ Sample request bodies for POST/PUT
- ✅ Filter examples for GET requests
- ✅ Environment variable for base URL
- ✅ Organized into logical folders
See POSTMAN_GUIDE.md for detailed usage instructions.
- View Database: Use
npm run prisma:studioto open a GUI for your database - Reset Database: Delete and recreate with
npx prisma migrate reset - Check Logs: Fastify logger is enabled by default
- Database Connection: Ensure Docker container is running before starting the app
- Controller Layer: HTTP request/response logic in
controllers/tradeSchoolController.js - Service Layer: All business logic and database operations in
services/tradeSchoolService.js - Schema Validation: All validation rules are in
schemas/tradeSchool.schema.js - Route Definitions: All routes are defined cleanly in
index.js - Validation Errors: Fastify automatically returns detailed validation errors
- Test with Postman: Import the Postman collection for easy API testing
- Add Schema (
schemas/) - Define request/response validation - Add Service Method (
services/) - Implement business logic - Add Controller Method (
controllers/) - Handle HTTP concerns - Add Route (
routes/) - Add to existing route file or create new one - Register Plugin (
index.js) - If new route file, register as plugin
Example: Adding a new feature area (e.g., "students")
// 1. Create routes/student.routes.js
export default async function studentRoutes(fastify, options) {
fastify.get('/students', { schema, handler: controller.getAll });
}
// 2. Register in index.js
fastify.register(studentRoutes, { prefix: '/api' });# Ensure PostgreSQL container is running
docker ps
# Restart container if needed
npm run docker:down
npm run docker:up# Reset database and run migrations
npx prisma migrate reset
npm run db:setup# Change PORT in .env file
PORT=3001- Add pagination for large datasets
- Implement authentication and authorization
- Add more detailed filtering options
- Include tuition information and program duration
- Add student reviews and ratings
- API versioning
- Rate limiting
- Caching layer (Redis)
- GraphQL endpoint
- OpenAPI/Swagger documentation
ISC