Skip to content

arifintahu/project-structure-api

Repository files navigation

Contributors Forks Stargazers Issues


Project Structure API

A production-ready project template for building RESTful APIs with TypeScript, Express, and Sequelize

Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Architecture
  3. Getting Started
  4. Docker
  5. Usage
  6. API Reference
  7. OpenAPI Spec
  8. Testing
  9. Project Structure
  10. Contributing

About The Project

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.

Built With

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

Features

  • Layered architecture — Routes → Controllers → Services → Repositories → Models
  • Security hardening — Helmet headers, rate limiting, body size limits, env-based CORS
  • Request tracingX-Request-Id UUID per request, included in logs and response headers
  • Structured logging — JSON format in production (for log aggregation), colorized in development
  • Health checkGET /health with database connectivity status
  • Graceful shutdown — Handles SIGTERM/SIGINT, drains connections, closes DB pool
  • DB connection pooling — Configurable Sequelize pool (min/max/acquire/idle)
  • Typed error handling — Custom AppError hierarchy with proper HTTP status codes
  • Consistent API responsesApiResponse utility for uniform success/error/paginated responses
  • Pagination — Built-in paginated list endpoints with ?page=1&limit=10
  • Password hashing — Automatic bcrypt hashing via Sequelize model hooks
  • Environment validation — Required env vars checked at startup with clear errors
  • JWT authentication — Token-based auth with role-based access control
  • OpenAPI 3.0 — Inline JSDoc specs co-located with routes + JSON spec export
  • Docker — Multi-stage build, non-root user, health check, Compose with PostgreSQL
  • CI/CD — GitHub Actions pipeline (lint → build → test)
  • Pre-commit hooks — Husky + lint-staged for ESLint and Prettier on commit

(back to top)

Architecture

Request → 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

(back to top)

Getting Started

Prerequisites

  • Node.js v22 or higher
  • PostgreSQL server running (or use Docker)
  • npm (comes with Node.js)

Installation

  1. Clone or use as template

    git clone https://github.com/arifintahu/project-structure-api.git
    cd project-structure-api

    Or click Use this template on GitHub.

  2. Install dependencies

    npm ci
  3. Configure environment

    cp .env.example .env

    Edit .env with your settings:

    NODE_ENV=development
    APP_NAME=my-project
    SERVER=development
    PORT=3001
    SECRET=your-secret-key
    API_VERSION=v1
    
    # CORS
    CORS_ORIGIN=*
    
    # Rate Limiting
    RATE_LIMIT_WINDOW_MS=900000
    RATE_LIMIT_MAX=100
    
    # Database
    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
    
    # Database Connection Pool
    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.

  4. Build the project

    npm run build
  5. Sync database tables

    npm run sync-db
  6. Start the server

    npm run start

    For development with auto-reload:

    npm run start-watch
  7. Verify

(back to top)

Docker

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:

  • app — Node API built with multi-stage Dockerfile (non-root user, health check)
  • db — PostgreSQL 16 Alpine with persistent volume and health check

Environment variables can be overridden via .env file or inline:

DB_PASSWORD=mysecret PORT=8080 docker compose up --build

(back to top)

Usage

Adding a New Resource

Follow this pattern to add a new resource (e.g., Product):

  1. Model — Create src/api/models/Product.ts with Sequelize schema
  2. Repository interface — Create src/api/repositories/interfaces/IProductRepository.ts
  3. Repository — Create src/api/repositories/ProductRepository.ts
  4. Service interface — Create src/api/services/interfaces/IProductService.ts
  5. Service — Create src/api/services/ProductService.ts
  6. Controller — Create src/api/controllers/ProductController.ts
  7. Validation — Add rules in src/api/middlewares/validator/requirements/
  8. Routes — Create src/api/routes/v1/products.ts with inline @swagger JSDoc, register in src/api/routes/v1/index.ts
  9. Tests — Add __test__/ folders in repositories and services

Error Handling

Use 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');

API Responses

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 }

Pagination

List endpoints accept query parameters:

GET /api/v1/users?page=2&limit=20

Default: page=1, limit=10.

Available Scripts

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

(back to top)

API Reference

Health

Method Endpoint Description Auth
GET /health Health check with DB status No

Authentication

Method Endpoint Description Auth
POST /api/v1/login Login with email/password No
POST /api/v1/signup Register new user No

Users

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

Roles

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.

(back to top)

OpenAPI Spec

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.

Swagger UI

Available in development at /docs/v1:

http://localhost:3001/docs/v1

JSON Spec Export

Get the raw OpenAPI JSON spec (useful for client SDK generation):

GET http://localhost:3001/docs/v1/spec.json

Use this with tools like:

  • openapi-generator — Generate client SDKs in any language
  • Postman — Import collection from OpenAPI spec
  • Redoc — Alternative API docs renderer

Adding Docs to a New Route

Add @swagger JSDoc comments directly above route definitions:

/**
 * @swagger
 * /products:
 *   get:
 *     summary: Get products
 *     description: Get paginated list of products
 *     tags: [Products]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           default: 10
 *     responses:
 *       200:
 *         description: Products fetched successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/PaginatedResponse'
 */
productsRouter.route('/').get(Auth.authenticate, ProductController.getProducts);

(back to top)

Testing

# Run all tests with coverage
npm test

# Run without coverage
npm run test:noCoverage

# Run specific test file
npx jest src/api/services/__test__/UserService.test.ts

Test Types

Type Location Description
Unit services/__test__/ Service business logic (mocked repos)
Unit repositories/__test__/ Repository data access (mocked models)
Integration routes/__test__/ Full HTTP request → response via supertest

Integration tests verify health check, request ID, security headers, authentication enforcement, and input validation.

(back to top)

Project Structure

├── .github/workflows/ci.yml       # GitHub Actions CI pipeline
├── .husky/                         # Git hooks (pre-commit: lint-staged)
├── src/
│   ├── @types/                     # Custom type declarations
│   ├── api/
│   │   ├── controllers/            # Request handling, response formatting
│   │   ├── middlewares/
│   │   │   ├── auth/               # JWT authentication & role checking
│   │   │   ├── handlers/           # Global error handler
│   │   │   ├── morgan/             # HTTP request logging (with request ID)
│   │   │   ├── requestId/          # X-Request-Id generation
│   │   │   └── validator/          # Input validation (express-validator)
│   │   ├── models/                 # Sequelize models & type definitions
│   │   ├── repositories/
│   │   │   ├── interfaces/         # Repository contracts
│   │   │   ├── __test__/           # Repository unit tests
│   │   │   └── *.ts                # Data access layer
│   │   ├── routes/
│   │   │   ├── v1/                 # Route definitions with inline @swagger docs
│   │   │   └── __test__/           # Integration tests (supertest)
│   │   ├── services/
│   │   │   ├── interfaces/         # Service contracts
│   │   │   ├── __test__/           # Service unit tests
│   │   │   └── *.ts                # Business logic layer
│   │   └── types/                  # Request DTOs & pagination types
│   ├── config/
│   │   ├── appConfig.ts            # Centralized configuration (CORS, rate limit, DB pool)
│   │   └── validateEnv.ts          # Startup environment validation
│   ├── constants/                  # App-wide constants
│   ├── database/                   # DB connection (with pool config) & table sync
│   ├── errors/
│   │   └── AppError.ts             # Typed error class hierarchy
│   ├── utils/
│   │   ├── jwt/                    # JWT sign/verify helpers
│   │   ├── logger/                 # Winston logger (JSON prod / colorized dev)
│   │   ├── response/               # ApiResponse utility
│   │   ├── swagger/                # OpenAPI config (scans route files)
│   │   └── helpers.ts              # Misc utilities
│   ├── server.ts                   # Express app (helmet, rate limit, CORS, health check)
│   └── index.ts                    # Entry point (graceful shutdown)
├── Dockerfile                      # Multi-stage build, non-root user
├── docker-compose.yml              # App + PostgreSQL 16
├── .dockerignore
├── eslint.config.mjs               # ESLint v9 flat config
├── jest.config.json                # Jest + ts-jest config
├── tsconfig.json                   # TypeScript config (ES2022, Node16)
└── package.json

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/feature-name)
  3. Commit your Changes (git commit -m 'Add some feature-name')
  4. Push to the Branch (git push origin feature/feature-name)
  5. Open a Pull Request

(back to top)

About

Complete project template for building RESTful API with Typescript.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages