GitHunt
FR

francemazzi/auth-boiler-plate

A quick boiler plate to creare a type safe Node server: npx create-express-auth my-app

Express Auth Boilerplate πŸš€

Express Auth Banner

TypeScript
Express.js
Prisma
PostgreSQL
Jest
Docker
Contributors

Production-ready authentication boilerplate built with TypeScript and Clean Architecture.
Get your secure API up and running in minutes! πŸ”₯

Quick Start β€’
Features β€’
Documentation β€’
Development

✨ Features

πŸ” Security

  • JWT Authentication with refresh tokens
  • Two-Factor Auth (2FA) with QR code support
  • Email Verification with secure tokens
  • Rate Limiting against DDoS attacks
  • CORS Protection with configurable origins
  • Password Hashing with bcrypt
  • XSS Protection with security headers

πŸ— Architecture

  • Clean Architecture principles
  • Domain-Driven Design patterns
  • Repository Pattern for data access
  • Error Handling with custom AppError
  • Dependency Injection ready

πŸ§ͺ Quality Assurance

  • 100% TypeScript coverage
  • Jest Testing with mocks
  • ESLint & Prettier configured
  • Git Hooks with Husky
  • CI/CD ready

🐳 Infrastructure

  • Docker Compose setup
  • PostgreSQL database
  • MailHog for email testing
  • Hot Reload development
  • Swagger UI documentation

πŸš€ Quick Start

# Create new project (npm)
npx create-express-auth my-app

# Or with Bun
bunx create-express-auth my-app

# Navigate and start services
cd my-app && docker-compose up -d

πŸ“š Documentation

Authentication API

POST /api/auth/register     # Create new account
POST /api/auth/login        # Get JWT token
GET  /api/auth/verify      # Verify email

Two-Factor Auth API

POST /api/otp/enable       # Enable 2FA
POST /api/otp/verify       # Verify OTP code
POST /api/otp/disable      # Disable 2FA

πŸ›  Development

# Using npm
npm run dev
npm test
npm run prisma:generate    # Generate Prisma client
npm run prisma:migrate     # Run migrations
npm run seed               # Seed database

# Using Bun
bun run dev
bun run test
bun run prisma:generate    # Generate Prisma client
bun run prisma:migrate     # Run migrations
bun run seed               # Seed database

πŸ“¦ Project Structure

src/
β”œβ”€β”€ application/          # Business Logic Layer
β”‚   └── use-cases/       # Application Use Cases
β”‚
β”œβ”€β”€ domain/              # Domain Layer
β”‚   β”œβ”€β”€ entities/        # Business Objects
β”‚   β”œβ”€β”€ repositories/    # Data Contracts
β”‚   └── errors/          # Error Handling
β”‚
β”œβ”€β”€ infrastructure/      # Infrastructure Layer
β”‚   β”œβ”€β”€ http/           # Express Setup
β”‚   β”‚   β”œβ”€β”€ controllers/  # Request Handlers
β”‚   β”‚   β”œβ”€β”€ middlewares/ # HTTP Pipeline
β”‚   β”‚   └── routes/      # API Routes
β”‚   └── services/       # External Services
β”‚
└── test/               # Test Suites

🧱 Hexagonal Architecture (Ports & Adapters)

This project follows Hexagonal Architecture (a.k.a. Ports & Adapters) to keep the core business independent from frameworks, databases and I/O details.

  • Domain (Core): business rules expressed via entities, errors, and domain types.
  • Application: orchestration of use-cases (application services) that coordinate domain logic and ports.
  • Infrastructure: adapters for the outside world (HTTP controllers, repositories implementation, email/otp services, DB, etc.).

Principles

  • Dependency Rule: inner layers don’t depend on outer layers. domain knows nothing about HTTP/DB; application depends on domain, never the opposite.
  • Ports: interfaces in domain/repositories (and service contracts) define what the core needs from the outside.
  • Adapters: concrete implementations in infrastructure satisfy those ports (e.g., PrismaUserRepository implements IUserRepository).
  • Thin Controllers: controllers should not contain business logic; they validate/parse input and delegate to use-cases or repositories based on complexity.

Use-Cases: When to Use Them

Use-cases are application services meant for flows that are more than a trivial CRUD operation.

Create a use-case when you have one or more of the following:

  • Business Orchestration: multiple steps, transactions, or calls across repositories/services.
  • Domain Invariants: validations and rules that must be enforced consistently.
  • Side Effects: sending emails, publishing events, generating tokens, etc.
  • Cross-Cutting Concerns: idempotency, auditing, retries, compensations.

Avoid creating a use-case for very simple CRUD where the controller can safely call a repository method directly with minimal validation.

Examples

1) Simple CRUD (No Use-Case)

A very simple update that doesn’t require orchestration can call the repository directly from the controller.

// src/infrastructure/http/controllers/UserController.ts
import { Request, Response } from 'express';
import { IUserRepository } from '../../../domain/repositories/IUserRepository';

export class UserController {
  constructor(private readonly userRepository: IUserRepository) {}

  // Example: update display name is a trivial CRUD with minimal rules
  updateProfile = async (req: Request, res: Response) => {
    const { displayName } = req.body;
    // Minimal validation; no complex rules
    const user = await this.userRepository.updateDisplayName(req.user!.id, displayName);
    res.json({ id: user.id, displayName: user.displayName });
  };
}

When the logic is only β€œvalidate input β†’ persist β†’ return”, a dedicated use-case often adds unnecessary indirection.

2) Complex Flow (Use-Case)

Changing a password (as example) touches security rules, hashing, and validations. This is a good candidate for a use-case.

// src/application/use-cases/auth/ChangePasswordUseCase.ts
import { IUserRepository } from '../../../domain/repositories/IUserRepository';
import { AppError } from '../../../domain/errors/AppError';

export interface PasswordHasher {
  hash(plain: string): Promise<string>;
  verify(plain: string, hashed: string): Promise<boolean>;
}

export class ChangePasswordUseCase {
  constructor(
    private readonly userRepository: IUserRepository,
    private readonly hasher: PasswordHasher,
  ) {}

  async execute(input: {
    userId: string;
    currentPassword: string;
    newPassword: string;
  }): Promise<void> {
    const user = await this.userRepository.findById(input.userId);
    if (!user) throw new AppError('User not found', 404);

    const isValid = await this.hasher.verify(input.currentPassword, user.password);
    if (!isValid) throw new AppError('Invalid credentials', 401);

    const newHashed = await this.hasher.hash(input.newPassword);
    await this.userRepository.updatePassword(user.id, newHashed);
  }
}

Controller delegates to the use-case, keeping HTTP concerns separate from business orchestration:

// src/infrastructure/http/controllers/AuthController.ts
import { Request, Response } from 'express';
import { ChangePasswordUseCase } from '../../../application/use-cases/auth/ChangePasswordUseCase';

export class AuthController {
  constructor(private readonly changePassword: ChangePasswordUseCase) {}

  changePasswordHandler = async (req: Request, res: Response) => {
    await this.changePassword.execute({
      userId: req.user!.id,
      currentPassword: req.body.currentPassword,
      newPassword: req.body.newPassword,
    });
    res.status(204).send();
  };
}

This mirrors existing use-cases like RegisterUseCase, LoginUseCase, VerifyEmailUseCase, and OTP flows, which coordinate repositories and external services (email, OTP) while enforcing domain rules.

πŸ”§ Environment Variables

# Server
PORT=8081
NODE_ENV=development

# Database
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/auth-boiler-plate

# JWT
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1d

# Email
SMTP_HOST=mailhog
SMTP_PORT=1025

πŸ§ͺ Integration Tests

This project includes integration tests that run against a real Postgres (e.g., via docker-compose) and exercise the API routes using Supertest. Remember that database server should run.

# Start services (Postgres, MailHog)
npm run docker:up

# Run integration suite
npm run test:integration

Integration tests live in test-integration/. They reuse the actual Express app via src/infrastructure/http/appFactory.ts without starting an HTTP listener, and clean the DB between tests.

🌐 Available Services

πŸ“ License

MIT Β© Francesco Mazzi


πŸ‘₯ Contributors

Thanks to all contributors!