An open API service indexing awesome lists of open source software.

https://github.com/sushantrahate/express-typescript-prisma-postgresql

For building RESTful APIs using Express, TypeScript, Prisma, and PostgreSQL.
https://github.com/sushantrahate/express-typescript-prisma-postgresql

eslint express nodejs postgresql prisma typescript

Last synced: 10 months ago
JSON representation

For building RESTful APIs using Express, TypeScript, Prisma, and PostgreSQL.

Awesome Lists containing this project

README

          

# πŸš€ Express + TypeScript + Prisma + PostgreSQL Boilerplate (2025 Edition)

This is a backend built with Node.js, Express, TypeScript, and Prisma ORM. It follows modern best practices for API development, including strict type safety, structured error handling, security measures, and environment validation.

Designed to be modular and maintainable, the project features a clean architecture, making it easy to extend with new functionalities.

## ✨ Features

πŸ› οΈ Core Features\
βœ… TypeScript – Fully typed backend for maintainability\
βœ… Express.js – Lightweight and fast web framework\
βœ… Prisma ORM – Type-safe database interactions
βœ… PostgreSQL – Relational database\
\
🎯 Development & Code Quality\
βœ… Feature-Based Structure – Each feature has its own folder, keeping everything related to a feature (routes, schemas, types, services, controllers, repositories) together for better maintainability and scalability\
βœ… ESLint + Prettier – Code linting, formatting and autoformat on save\
βœ… Zod Validation – Strict schema validation for request & environment variables\
βœ… VSCode debugger\
\
πŸ” Environment & Security\
βœ… Environment Validation – Ensures required .env variables exist\
βœ… Helmet & Security Headers – Protects against web vulnerabilities\
βœ… Rate Limiter, Host whitelisting middleware\
\
⚑ API & Middleware\
βœ… Request Validation – Uses Zod for body, params, and query validation\
βœ… Error Handling Middleware – Centralized error handling with PostgreSQL error handling [(Ref)](https://www.prisma.io/docs/orm/reference/error-reference)\
βœ… Unified Response Structure – Uses [uni-response](https://github.com/sushantrahate/uni-response) for consistent API responses\
\
πŸ§ͺ Testing & CI/CD\
βœ… Vitest – Unit and integration testing\
βœ… Husky + Lint-Staged – Enforces pre-commit linting and testing\
\
πŸ›‘ Server Management\
βœ… Graceful Shutdown – Ensures proper cleanup of database & open connections during shutdown [(Ref)](https://github.com/sushantrahate/secure-nodejs-backend/tree/main/graceful-shutdown)

## πŸ› οΈ Clean Architecture & Feature-Based Structure

### πŸ“Œ Clean Architecture & Framework-Agnostic Design

This project follows a feature-based modular structure, where each feature (e.g., user) has its own isolated folder containing everything related to that feature.

πŸ“‚ Project Structure:

```bash
src/
│── config/ # Configuration (e.g., environment variables, Prisma, security)
│── constants/ # Shared constants (messages, enums, etc.)
│── features/ # Feature-based modular structure
β”‚ β”œβ”€β”€ user/ # User feature module
β”‚ β”‚ β”œβ”€β”€ __tests__/ # Unit tests (vitest)
β”‚ β”‚ β”œβ”€β”€ controllers/ # Handles HTTP requests (Express-dependent)
β”‚ β”‚ β”œβ”€β”€ repositories/ # Database interactions (Prisma-dependent)
β”‚ β”‚ β”œβ”€β”€ routes/ # Express API routes (Express-dependent)
β”‚ β”‚ β”œβ”€β”€ schemas/ # Zod validation schemas (Framework-agnostic)
β”‚ β”‚ β”œβ”€β”€ services/ # Business logic (Completely framework-independent)
β”‚ β”‚ β”œβ”€β”€ types/ # TypeScript interfaces & types
│── middleware/ # Global Express middlewares
│── utils/ # Helper functions
│── app.ts # Express app setup
│── server.ts # Entry point
```
### πŸ“Œ Layer-by-Layer Breakdown

### 1️⃣ Feature Modules (e.g., user/)

Each feature is self-contained, meaning everything related to "users" is inside `features/user/`

🎯 Benefit:\
πŸ’‘ You can easily add or remove features without affecting other parts of the app.

πŸ”Ή **No Cluttering, Even as the Project Grows Large –** The feature-based structure ensures that related files stay together, preventing scattered code.\
πŸ”Ή **Everything in One Place –** Developers can find all logic related to a feature (controllers, services, repositories, schemas) in a single folder, reducing confusion.\
πŸ”Ή **No Ambiguity in Large Systems –** Since each feature is self-contained, developers always know which controller, service, or repository to use, making onboarding and scaling easier.\
πŸ”Ή **Scalability & Maintainability –** Adding a new feature means simply creating a new folder under features/, without modifying unrelated parts of the app.

### 2️⃣ Controllers (controllers/)

βœ… Handles HTTP requests and responses\
βœ… Calls the service layer for business logic\
βœ… Only responsible for Express-specific logic

πŸ“„ Example: user.controller.ts

```ts
import { Request, Response } from "express";
import { UserService } from "../services/user.service";

export class UserController {
private userService: UserService;

constructor() {
this.userService = new UserService();
}

async getUsers(req: Request, res: Response) {
const users = await this.userService.getAllUsers();
res.json({ success: true, data: users });
}
}
```

### πŸ› οΈ Why This Structure?\

Express-specific logic stays here (e.g., req, res)\
Business logic is in the service layer (so it’s framework-agnostic)

🎯 Benefit:\
πŸ’‘ Can switch from Express to Fastify/NestJS by just changing the controllers.

### 3️⃣ Services (services/)

βœ… Contains core business logic\
βœ… Does NOT depend on Express or Prisma\
βœ… Interacts with repositories for data retrieval\

πŸ“„ Example: user.service.ts

```ts
import { UserRepository } from "../repositories/user.repository";

export class UserService {
private userRepository: UserRepository;

constructor() {
this.userRepository = new UserRepository();
}

async getAllUsers() {
return await this.userRepository.getUsers();
}
}
```

### πŸ› οΈ Why This Structure?

- No dependency on Express or HTTP requests
- Calls repository for database access

🎯 Benefit:\
πŸ’‘ Can be reused in a CLI app, background worker, or GraphQL API without changes.

### 4️⃣ Repositories (repositories/)

βœ… Handles all database queries\
βœ… Uses Prisma (or any ORM, easily replaceable)\
βœ… Interacts only with services/, never controllers

πŸ“„ Example: user.repository.ts

```ts
import { prisma } from "@/config/prisma.config";

export class UserRepository {
async getUsers() {
return await prisma.user.findMany();
}
}
```

### πŸ› οΈ Why This Structure?

- Keeps database logic separate from business logic
- Easy to swap Prisma for another ORM (e.g., Drizzle, TypeORM)

🎯 Benefit:\
πŸ’‘ Can change the database or ORM without affecting services/controllers.

### 5️⃣ Routes (routes/)

βœ… Defines API endpoints\
βœ… Maps controllers to Express routes

πŸ“„ Example: user.routes.ts

```ts
import { Router } from "express";
import { UserController } from "../controllers/user.controller";

const router = Router();
const userController = new UserController();

router.get("/", (req, res) => userController.getUsers(req, res));

export default router;
```

### πŸ› οΈ Why This Structure?

- Controllers are injected into routes for better testability
- Only Express-dependent part is here

🎯 Benefit:\
πŸ’‘ Can switch to NestJS, Fastify, or Hono by only changing routes & controllers.

### 6️⃣ Validation Schemas (schemas/)

βœ… Uses Zod for request validation
βœ… Completely framework-independent

πŸ“„ Example: user.schema.ts

```ts
import { z } from "zod";

export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().optional(),
});
```

### πŸ› οΈ Why This Structure?

- Schemas don’t depend on Express, so they can be used anywhere
- Validation logic is reusable (can be used in GraphQL, CLI, or workers)
-
🎯 Benefit:\
πŸ’‘ Easier to enforce validation rules across different application layers.

### πŸ› οΈ Final Benefits Summary



Layer
Purpose
Benefit




Controllers
Handle HTTP requests
Framework-dependent, easily replaceable


Services
Business logic
Framework-agnostic, reusable anywhere


Repositories
Database interactions
Can switch ORM (Prisma, TypeORM, Drizzle)


Routes
Maps controllers to APIs
Only responsible for Express routing


Schemas
Data validation
Reusable validation logic across app

## ✨ Setup from scratch

## ⚑ TypeScript & Development Dependencies Setup

```bash
mkdir express-ts-prisma && cd express-ts-prisma
npm init -y
```

```bash
npm install --save-dev typescript tsx nodemon @types/node tsc-alias
```

> Create `tsconfig.json`

## ⚑ Add Express, CORS, and .env Setup

```bash
npm install express cors dotenv
npm install --save-dev @types/express @types/cors
```

> Create `.env.dev` File from `.env.example`
> Create src/config/env-config.ts // Env Configuration file
> Create src/config/env-schema.ts // Schema for environment variables

## ⚑ ESLint, Prettier & Linting Plugins

```bash
npm install --save-dev eslint prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-prettier eslint-plugin-node eslint-plugin-import eslint-plugin-simple-import-sort eslint-plugin-unicorn eslint-plugin-security eslint-config-prettier
```

> Create `eslint.config.js`

> Create `.prettierrc.json`

> Create `.prettierignore`

πŸ“Œ Prettier will ignore these files & folders (same format as `.gitignore`).

> create `.vscode/settings.json` to Autoformat using Prettier on save

## ⚑ Setup Prisma & PostgreSQL

> Create Database and Shadow Database

> Update `.env.dev` File

```ini
DATABASE_URL="postgresql://dev_user:dev_password@localhost:5432/dev_db"
SHADOW_DATABASE_URL="postgresql://dev_user:dev_password@localhost:5432/dev_db_shadow"
```

### Install Prisma

```bash
npm install @prisma/client
npm install --save-dev prisma
```

### Initialize Prisma

```bash
npx prisma init
```

### Modify prisma/schema.prisma

```js
model User {
id String @id @default(uuid())
email String @unique
name String?
createdAt DateTime @default(now())
}
```

### Run Migrations

```bash
npx prisma generate
npx prisma migrate dev --name init
```

## ⚑ Create Express Server

### Create `src/server.ts`

## ⚑ Setup Husky + Lint-Staged

```bash
npm install --save-dev husky lint-staged
```

### Enable Husky

```bash
npx husky install
npm set-script prepare "husky install"
```

### Add Pre-commit Hook

```bash
npx husky add .husky/pre-commit "npx lint-staged"
```

Modify `package.json`

```json
// Runs linters (ESLint, Prettier) only on changed files before committing.
"lint-staged": {
"**/*.{ts,json,md}": ["eslint --fix", "prettier --write"]
}
```

Add Pre-Push Hook

```sh
// Before git push trigger tests & build validation.
npx husky add .husky/pre-push "npm run lint && npm run format && npm run test && npm run build"
```

## ⚑ Add Scripts in package.json

```json
"scripts": {
"prebuild": "npm run lint && npm run format && npm run test",
"build": "tsc",
"start": "node dist/server.js",
"dev": "nodemon --ext ts --exec tsx src/server.ts",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext ts --fix",
"format": "prettier --write .",
"test": "vitest",
"prepare": "npx husky install"
}
```

## ⚑ Run the Project

```bash
# Start Dev Server
npm run dev

# Lint Code
npm run lint
npm run lint:fix

# Format Code
npm run format
```

## ⚑ Vitest for Unit Testing

```bash
npm install --save-dev vitest @vitest/coverage-v8 @types/jest supertest @types/supertest
```

Create test files at `src\features\user\__tests__`

## ⚑ Security

```sh
npm i helmet express-rate-limit
```

## ⚑ Logger

```bash
npm install pino pino-pretty pino-http
npm install -D @types/pino @types/pino-pretty @types/pino-http
```

Create src\middleware\pino-logger.ts

## ⚑ Constants

## ⚑ Middleware

## ⚑ Utils

If you liked it then please show your love by ⭐ the repo