A modern boilerplate for building scalable and maintainable REST APIs with authentication, written in TypeScript. It features Docker, Express, TypeORM, Passport, and integrates Clean Architecture principles with Dependency Injection powered by Inversify.
Made with ❤️ by Alex Le Boucher and contributors
The main goal of this boilerplate is to provide a robust foundation for building a scalable and maintainable REST API with Express, following modern development practices.
It integrates common features such as:
- Docker containerization
- Database connection (PostgreSQL with TypeORM)
- Authentication (using Passport)
- Centralized error handling
- Clean Architecture principles for better separation of concerns
- Dependency Injection powered by Inversify for modular and testable code
Some basic routes for authentication and user creation are already implemented, allowing you to quickly start your project. The architecture is designed to be extensible, making it easy to add new features or adapt it to your needs. For more details on the already implemented routes, see API Routes.
End-to-end and unit tests are already implemented, ensuring a robust testing strategy. The code coverage is over 90%, providing high confidence in the stability and reliability of the boilerplate.
Packages are frequently upgraded. You can easily see the packages version status here.
⭐ If you like it, please leave a star, it helps me a lot! ⭐
- Docker containerization to easily run your code anywhere and avoid installing tools like PostgreSQL on your computer.
- Authentication with Passport.
- Authentication session thanks to express-session and connect-pg-simple.
- Simplified Database Query managed by TypeORM.
- Object-oriented database model with TypeORM entities.
- Integrated Testing Tools with Jest.
- Unit tests and end-to-end (E2E) tests implemented using Supertest.
- Tests utilities to simplify the creation of new tests.
- High code coverage (>90%) to ensure reliability and stability.
- Clean Architecture principles for better separation of concerns and scalability.
- Dependency Injection powered by Inversify, ensuring a modular and testable code structure.
- Controller management with inversify-express-utils for clean and structured route definitions.
- Route protection with custom middleware.
- Error handling with centralized and consistent middleware.
- Basic Security Features with Helmet and cors.
- Schema validation thanks to Zod.
- Transactions control with TypeORM.
- Configurated Code Linter with ESLint and common rules.
- HTTP Request logging with morgan.
- Migration generation based on entity changes thanks to TypeORM.
- Getting Started
- Scripts
- API Routes
- Project Structure
- Environment Variables
- Authentication
- Migrations
- Tests
- HTTP Request Logging
- Common Errors
- Clean Github Templates and Workflows
- Upcoming Features
- Further Documentations
- Contributing
- License
git clone https://github.com/alexleboucher/docker-express-postgres-boilerplate
cd docker-express-postgres-boilerplate
rm -rf .git # Windows: rd /s /q .git
yarn installNote:
rm -rf .git (or rd /s /q .git on Windows) deletes the git info of the branch like history.
macOS/Linux:
cp .env.example .envWindows:
copy .env.example .envStart the application in a Docker container and run the development server:
yarn docker:up
yarn devThis starts a local server using
nodemon, which automatically restarts the server when file changes are detected. The server will run onhttp://localhost:8000.
To verify the server is running:
- Open your browser and navigate to
http://localhost:8000/health. - Alternatively, use a tool like Postman to query the endpoint.
You should see the response:
{ "success": true }Create the necessary database tables by running the migrations:
yarn migration:runThe project contains Github templates and workflows. If you don't want to keep them, you can easily delete them by following this section.
backend container shell.
- Run
yarn docker:upto start the containers defined indocker-compose.yml. It automatically opens a shell in thebackendcontainer. Inside this shell, you can execute other scripts likeyarn dev. - Run
yarn docker:test:upto start the containers in test mode. - Run
yarn docker:downto stop all running containers. - Run
yarn docker:shellto open a shell inside thebackendcontainer. - Run
yarn docker:buildto build a Docker image of your API.
- Install all dependencies with
yarn install.
- Run
yarn devto start the application in development mode using nodemon. It automatically reloads the server when file changes are detected. By default, the server will run onhttp://localhost:8000.
- Run
yarn buildto compile the project. The output files will be placed in thebuild/directory. - Run
yarn startto start the compiled project. - Run
yarn type-checkto perform type checking.
- Run
yarn migration:runto execute all pending migrations. - Run
yarn migration:generate MigrationNameto generate a migration based on the current entity changes. - Run
yarn migration:create MigrationNameto create an empty migration file. - Run
yarn migration:revertto undo the last executed migration. To revert multiple migrations, execute the command multiple times.
- Run
yarn lintto analyze code quality using ESLint. This displays warnings and errors. - Run
yarn lint:fixto automatically fix linting issues where possible.
- Run
yarn testto execute the unit and integration tests. - Run
yarn test:coverageto execute tests and generate a coverage report.
| Method | Route | Description | Body |
|---|---|---|---|
| GET | /health |
Retures the server health status | None. |
| POST | /users |
Creates a new user. | username (min. 5 chars), email (valid), password (min. 8 chars). |
| POST | /auth/login |
Logs in a user. | email and password. |
| POST | /auth/logout |
Logs out the currently authenticated user. | None. |
| GET | /auth/authenticated |
Returns the user authentication status | None. |
| Name | Description |
|---|---|
| @types/ | Global types definitions |
| build/ | Compiled source files will be placed here |
| coverage/ | Jest coverage results will be placed here |
| src/ | Source files |
| src/app/ | Application layer containing controllers, middlewares, and request handlers. |
| src/app/controllers/ | REST API controllers implemented with inversify-express-utils. |
| src/app/middlewares/ | Custom Express middlewares for authentication, error handling, etc. |
| src/app/request-handlers/ | Handlers for processing API requests following the Clean Architecture principles. Organized into commands and queries. |
| src/app/server.ts | Express server configuration and initialization. |
| src/container/ | Dependency Injection container setup with InversifyJS. |
| src/core/ | Core utilities, interfaces, and helpers used across the project. |
| src/domain/ | Domain layer containing business logic, models, and interfaces. |
| src/domain/models/ | Domain models representing business entities. |
| src/domain/repositories/ | Interfaces for database operations. |
| src/domain/services/ | Interfaces for domain-level services (e.g., authentication, encryption). |
| src/domain/use-cases/ | Use cases implementing business logic. |
| src/infra/ | Infrastructure layer providing implementations for core and domain abstractions. |
| src/infra/auth/ | Authentication implementations using Passport.js and session management. |
| src/infra/database/ | Database configuration, models, and migrations. |
| src/infra/database/repositories/ | Concrete implementations of domain repository interfaces using TypeORM. |
| src/infra/id-generator/ | UUID-based ID generator. |
| src/infra/security/ | Security utilities like password encryption. |
| src/infra/logger/ | Logger implementations. |
| src/tests/ | Test suite, including unit and end-to-end tests. |
| src/tests/e2e/ | End-to-end tests for API routes. |
| src/tests/units/ | Unit tests for individual modules and layers. |
| src/tests/helpers/ | Utilities for simplifying test setup and assertions. |
| Name | Description | Optional | Default value |
|---|---|---|---|
| NODE_ENV | Specifies the environment (e.g., production, development, test). | ❌ | |
| HOST | Server host. | ✔️ | 0.0.0.0 |
| PORT | Server host port. | ✔️ | 8080 |
| DB_USER | Database username. | ❌ | |
| DB_HOST | Database host. | ❌ | |
| DB_NAME | Database name. | ❌ | |
| DB_PASSWORD | Database password. | ❌ | |
| DB_PORT | Database host port. | ❌ | |
| DB_HOST_PORT | Database mapped port for accessing the database in Docker. | ❌ | |
| TEST_DB_HOST | Test database host. | ❌ | |
| TEST_DB_NAME | Test database name. | ❌ | |
| TEST_DB_PORT | Test database host port. | ❌ | |
| TEST_DB_HOST_PORT | Test database mapped port for accessing the test database in Docker. | ❌ | |
| CORS_ORIGIN_ALLOWED | List of allowed origins for CORS. | ✔️ | * |
| SESSION_SECRET | Secret key for signing the session ID cookie. | ✔️ | session-secret |
| SESSION_RESAVE | Forces the session to be saved back to the session store, even if it was never modified. | ✔️ | false |
| SESSION_SAVE_UNINITIALIZED | Forces an uninitialized session to be saved to the store. | ✔️ | false |
| SESSION_COOKIE_SECURE | Ensures the cookie is only sent over HTTPS. | ✔️ | false |
| SESSION_COOKIE_MAX_AGE | Lifetime of the session cookie in milliseconds. | ✔️ | 7776000000 (90 days) |
| SESSION_COOKIE_HTTP_ONLY | Ensures the cookie is inaccessible to JavaScript (for XSS protection). | ✔️ | false |
| SESSION_COOKIE_SAME_SITE | Controls whether the cookie is sent with cross-site requests. | ✔️ | lax |
| DB_LOGGING | Enables or disables query logging in TypeORM. | ✔️ | false |
| TYPEORM_ENTITIES | Path to TypeORM entity files. | ✔️ | src/infra/database/models/**/*.entity.ts |
| TYPEORM_MIGRATIONS | Path to TypeORM migration files. | ✔️ | src/infra/database/migrations/**/*.ts |
| LOGGER_TYPE | Specifies the type of logger to use | ✔️ | console |
This boilerplate uses Passport.js to handle authentication. Passport.js is a powerful, flexible, and modular middleware that allows you to implement various authentication strategies, including social logins (e.g., Google, Facebook, GitHub, etc.).
The configuration for Passport is located in src/infra/auth/authenticator/passport-authenticator.ts. This class centralizes the setup of strategies and the implementation of required methods like serializeUser and deserializeUser.
serializeUser: Defines what data should be stored in the session. By default, it stores the user ID.deserializeUser: Fetches user information based on the session data and assigns it toreq.user. This makes the authenticated user readily accessible viareq.userwithout requiring additional calls.
You can find detailed documentation on Passport.js here.
To ensure route security and verify the user's authentication status, this boilerplate provides a custom middleware:
AuthenticatedMiddleware, located in src/app/middlewares/authenticated-middleware.ts.
This middleware ensures the user is authenticated before allowing access to the route. It integrates seamlessly with the controllers, as shown in the example below:
@httpPost('/logout', MIDDLEWARES_DI_TYPES.AuthenticatedMiddleware)
public logout(): void {
// Logout logic here
}This pattern allows you to secure endpoints declaratively and keeps the authentication logic consistent throughout the project.
Adding new strategies is straightforward thanks to Passport's modular design. To include a new strategy:
- Install the corresponding Passport strategy package (e.g.,
passport-google-oauth20). - Configure the strategy in
passport-authenticator.tsby adding it to the existing strategies.
This design simplifies the addition of new authentication methods and scales well as your application grows.
This boilerplate uses TypeORM to handle database migrations efficiently. Migrations allow you to track and apply changes to your database schema in a controlled and versioned manner.
All executed migrations are recorded in a dedicated database table, which enables TypeORM to:
- Identify pending migrations that need to be executed.
- Revert specific migrations if necessary.
backend container shell.
To create a new migration, run the following command:
yarn migration:create MigrationNameThis will create an empty migration file in src/infra/database/migrations/. A migration file contains two functions:
up: Defines the changes to be applied to the database when the migration is executed.down: Defines how to revert the changes applied by theupfunction.
You must manually define the logic for both functions when creating an empty migration.
TypeORM also allows you to generate migrations automatically based on changes in your entities. To generate a migration:
yarn migration:generate MigrationNameThis will create a migration file in src/infra/database/migrations/. The content of the migration will be automatically generated by comparing your updated entities with the current database schema.
Exemple:
- Add a new property
firstNameto theUserentity:
@Column({ nullable: false, length: 20 })
firstName!: string;- Run:
yarn migration:generate AddFirstNameInUser- A migration will be generated to add the
firstNamecolumn to theuserstable.
To execute pending migrations and update your database schema:
yarn migration:runThis will run all migrations that have not yet been applied to the database.
To revert the last executed migration, run:
yarn migration:revertIf you need to revert multiple migrations, you can execute this command multiple times. Each execution will revert one migration in reverse order of their execution.
Tests are located in src/tests.
In this boilerplate, Jest is used to execute the tests.
The tests are divided into two types: end-to-end tests and unit tests.
- Run all tests:
yarn testExecute this command in the backend container shell after running command yarn docker:test:up
- Run tests with coverage:
yarn test:coverage- Run a specific test file: Add the name or path of the file after the command. For example:
yarn test authEnd-to-end (E2E) tests validate the application’s behavior in product-like scenarios. They simulate user workflows and interactions with the API to ensure that the entire system functions as expected.
E2E tests are implemented using supertest and located in src/tests/e2e/.
This project prioritizes unit testing over E2E testing. E2E tests primarily cover happy paths and critical features.
-
Setup Test Environment: Create the test environment before executing your tests:
let testEnv: TestEnvironment; beforeAll(async () => { testEnv = await createTestEnvironment(); }); afterAll(async () => { await testEnv.close(); });
-
Clear Database (if needed): If the test requires a clean database state, clear the database before each test:
beforeEach(async () => { await testEnv.clearDatabase(); });
If your test does not require database connection (e.g.,
/healthor404routes), you can disable it:testEnv = await createTestEnvironment({ connectDatabase: false });
-
Write Tests: Use
testto define individual tests withindescribeblocks:test('returns a 200 status for /health', async () => { const res = await testEnv.request().get('/health'); expect(res.statusCode).toEqual(200); });
-
Authenticated Requests: For tests requiring user authentication, create an authenticated agent:
const { agent } = await testEnv.createAuthenticatedAgent(); const res = await agent.get('/auth/authenticated'); expect(res.body).toEqual({ authenticated: true });
Unit tests focus on testing individual components or functions in isolation. They ensure the correctness of the smallest parts of your application, such as use cases, services, or utility functions.
Unit tests are located in src/tests/unit/.
-
Mock Dependencies: Use libraries like
jest-mock-extendedto create mocks:import { mock } from 'jest-mock-extended'; const userRepository = mock<IUserRepository>(); userRepository.existsByEmail.mockResolvedValue(false);
-
Write Tests: Create descriptive test cases within
describeblocks:describe('CreateUserUseCase', () => { test('creates a user successfully', async () => { // Setup mocks and inputs const userRepository = mock<IUserRepository>(); userRepository.create.mockResolvedValue(mockedUser); const useCase = new CreateUserUseCase(userRepository); const result = await useCase.execute(payload); expect(result).toBeInstanceOf(Success); }); });
-
Test Failure Scenarios: Include tests for error cases or edge conditions:
test('fails if email already exists', async () => { userRepository.existsByEmail.mockResolvedValue(true); const result = await useCase.execute(payload); expect(result).toBeInstanceOf(Failure); });
To log HTTP requests, we use the express middleware morgan.
You can easily configure it by passing a predifined format as parameter in src/config/express.ts.
Example:
app.use(morgan('short'));If you encounter an error when running yarn docker:up, make sure Docker Desktop is running.
If you encounter an error when running a script, make sure you ran the script in the backend container shell.
If you don't want to keep the issue templates, you can delete the folder ISSUE_TEMPLATE in .github folder.
If you don't want to keep the Pull Request template, you can delete the file pull_request_template.md in .github folder.
There are 3 workflows:
-
The workflow
pull-requestconcerns the pull requests. It checks linting, build, runs tests and sends the coverage to Codecov. If you don't want to keep it, you can delete the filepull-request.ymlin the folderworkflowsin.github. -
The workflow
main-testsis triggered when code is merged or pushed on main. It runs the tests and sends the coverage to Codecov. It has coverage for the main branch. If you don't want to keep it, you can delete the filemain-tests.ymlin the folderworkflows.
If you want to keep the tests on pull request but don't want to use Codecov, you can delete main-tests and only delete the last step Upload coverage to Codecov in pull-request.yml. You can also delete codecov.yml.
But if you want to use CodeCov, the only thing you need to do is set your CODECOV_TOKEN in your github secrets.
- The workflow
main-buildis triggered when something is merged or pulled on main. It builds the project and its primary goal is to check if main is building. If you don't want to keep it, you can delete the filemain-build.ymlin the folderworkflows.
This repository contains CONTRIBUTING, CODE_OF_CONDUCT and LICENSE files, you can delete them too.
You can see the upcoming or in progress features here.
| Name & Link | Description |
|---|---|
| Express | Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. |
| TypeORM | TypeORM is highly influenced by other ORMs, such as Hibernate, Doctrine and Entity Framework. |
| Passport | Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. |
| Docker | Docker is a platform designed to help developers build, share, and run modern applications. We handle the tedious setup, so you can focus on the code. |
| PostgreSQL | PostgreSQL is a powerful, open source object-relational database system with over 35 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. |
| TypeScript | TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. |
| Inversify | Inversify is a lightweight and flexible inversion of control (IoC) container. It helps in managing dependencies and achieving loose coupling in applications. |
| Jest | Jest is a Testing Framework with a focus on simplicity. |
| supertest | A library that allows developers and testers to test the APIs. |
| Helmet | Helmet helps you secure your Express apps by setting various HTTP headers. |
| cors | CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options. |
| ESLint | ESLint is a tool for identifying and reporting on patterns found in code, with the goal of making code more consistent and avoiding bugs. |
Anyone and everyone is welcome to contribute.
If you want to propose new features, fix a bug or improve the README, don't hesitate to open a pull request. If your changes concern new feature or bugfix, please open an issue before.
However, if you decide to get involved, please take a moment to review the guidelines.
