[![Contributors][contributors-shield]][contributors-url] [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] [![Issues][issues-shield]][issues-url]
A production-ready project template for building RESTful APIs with TypeScript, Express, and Sequelize
<a href="https://github.com/arifintahu/project-structure-api/issues">Report Bug</a>
·
<a href="https://github.com/arifintahu/project-structure-api/issues">Request Feature</a>
Table of Contents
A clean, scalable Node.js API boilerplate with proper separation of concerns. Built with modern tooling (Node 22+, TypeScript 5.7, ESLint 9) and production-grade features including security hardening, structured logging, health checks, graceful shutdown, and CI/CD.
| Category | Technology |
|---|---|
| Runtime | Node.js v22+ |
| Language | TypeScript 5.7 |
| Framework | Express.js 4.x |
| ORM | Sequelize 6.x |
| Database | PostgreSQL |
| Auth | JSON Web Token + bcrypt |
| Security | Helmet + express-rate-limit |
| Docs | Swagger (swagger-jsdoc + swagger-ui-express) |
| Testing | Jest + ts-jest + Supertest |
| Linting | ESLint 9 (flat config) + Prettier |
| CI/CD | GitHub Actions |
| Container | Docker + Docker Compose |
X-Request-Id UUID per request, included in logs and response headersGET /health with database connectivity statusSIGTERM/SIGINT, drains connections, closes DB poolAppError hierarchy with proper HTTP status codesApiResponse utility for uniform success/error/paginated responses?page=1&limit=10Request → Route → Middleware (auth, validation) → Controller → Service → Repository → Model → DB
↓
ApiResponse (success/error)
| Layer | Responsibility | Example |
|---|---|---|
| Routes | HTTP method + URL mapping, middleware chaining | POST /api/v1/users |
| Middlewares | Auth, validation, rate limiting, request ID | Auth.authenticate, Validate(...) |
| Controllers | Parse request, call service, send response | UserController.createUser |
| Services | Business logic, validation rules | UserService.createUser |
| Repositories | Database queries via Sequelize | UserRepository.getUsers |
| Models | Schema definition, hooks, associations | User, Role |
Clone or use as template
sh
git clone https://github.com/arifintahu/project-structure-api.git
cd project-structure-api
Or click Use this template on GitHub.
Install dependencies
sh
npm ci
Configure environment
sh
cp .env.example .env
Edit .env with your settings:
```env NODE_ENV=development APP_NAME=my-project SERVER=development PORT=3001 SECRET=your-secret-key API_VERSION=v1
CORS_ORIGIN=*
RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX=100
DB_HOST=localhost DB_DATABASE=mydb DB_USERNAME=postgres DB_PASSWORD=postgres DB_PORT=5432 DB_DIALECT=postgres DB_TIMEZONE=Asia/Jakarta DB_LOG=true
DB_POOL_MIN=2 DB_POOL_MAX=10 ```
Required vars:
DB_HOST,DB_DATABASE,DB_USERNAME,DB_PASSWORD. The app will fail fast with a clear error if any are missing.
Build the project
sh
npm run build
Sync database tables
sh
npm run sync-db
Start the server
sh
npm run start
For development with auto-reload:
sh
npm run start-watch
Verify
Run the entire stack (API + PostgreSQL) with Docker Compose:
# Start services
docker compose up --build
# Start in background
docker compose up --build -d
# Stop services
docker compose down
# Stop and remove volumes
docker compose down -v
The Compose file includes:
Environment variables can be overridden via .env file or inline:
DB_PASSWORD=mysecret PORT=8080 docker compose up --build
Follow this pattern to add a new resource (e.g., Product):
src/api/models/Product.ts with Sequelize schemasrc/api/repositories/interfaces/IProductRepository.tssrc/api/repositories/ProductRepository.tssrc/api/services/interfaces/IProductService.tssrc/api/services/ProductService.tssrc/api/controllers/ProductController.tssrc/api/middlewares/validator/requirements/src/api/routes/v1/products.ts with inline @swagger JSDoc, register in src/api/routes/v1/index.ts__test__/ folders in repositories and servicesUse typed errors in services for proper HTTP status codes:
import { NotFoundError, ConflictError } from '../../errors/AppError';
// 404 - Resource not found
throw new NotFoundError('Product not found');
// 409 - Conflict (duplicate)
throw new ConflictError('Product SKU must be unique');
// 401 - Unauthorized
throw new UnauthorizedError('Invalid credentials');
// 403 - Forbidden
throw new ForbiddenError('Insufficient permissions');
// 422 - Validation error
throw new ValidationError('Invalid input data');
Controllers use ApiResponse for consistent response format:
import ApiResponse from '../../utils/response/ApiResponse';
// Standard response
ApiResponse.success(res, 'Product created', product, 201);
// Paginated response
ApiResponse.paginated(res, 'Products fetched', paginatedResult);
Response formats:
// Success
{ "message": "Product created", "data": { ... } }
// Paginated
{ "message": "Products fetched", "items": [...], "total": 50, "page": 1, "limit": 10, "totalPages": 5 }
// Error
{ "message": "Product not found", "statusCode": 404 }
List endpoints accept query parameters:
GET /api/v1/users?page=2&limit=20
Default: page=1, limit=10.
| Script | Description |
|---|---|
npm run start |
Start the server |
npm run start-watch |
Start with auto-reload (nodemon) |
npm run build |
Compile TypeScript to dist/ |
npm run build-watch |
Compile with watch mode |
npm run test |
Run tests with coverage |
npm run test:noCoverage |
Run tests without coverage |
npm run lint |
Run ESLint |
npm run prettier |
Format code with Prettier |
npm run sync-db |
Sync Sequelize models to database |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/health |
Health check with DB status | No |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/v1/login |
Login with email/password | No |
POST |
/api/v1/signup |
Register new user | No |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/v1/users?page=1&limit=10 |
List users (paginated) | Admin |
POST |
/api/v1/users |
Create user | Admin |
GET |
/api/v1/users/:id |
Get user details | Admin |
PUT |
/api/v1/users/:id |
Update user | Admin |
DELETE |
/api/v1/users/:id |
Delete user | Admin |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/v1/roles |
List roles | Admin |
POST |
/api/v1/roles |
Create role | Admin |
Use
Authorization: Bearer <token>header for authenticated endpoints.
API documentation uses OpenAPI 3.0 with inline @swagger JSDoc annotations co-located with route definitions. This means the spec stays in sync with the code automatically.
Available in development at /docs/v1:
http://localhost:3001/docs/v1
Get the raw OpenAPI JSON spec (useful for client SDK generation):
GET http://localhost:3001/docs/v1/spec.json
Use this with tools like:
Add @swagger JSDoc comments directly above route definitions:
```
$ claude mcp add project-structure-api \
-- python -m otcore.mcp_server <graph>