A modern Node.js boilerplate for building APIs with TypeScript, Vitest, Supertest and BiomeJS following best practices for code organization and testing.
- 🚀 Modern Node.js & TypeScript: Built with Node.js and TypeScript, utilizing the latest features and type safety
- 🧩 Colocated Code: Following Kent C. Dodds' article on colocation, keeping related code together
- 🧹 Code Quality:
- Biome for linting and formatting
- Pre-commit hooks with lefthook
- Commit message validation with commitlint
- 🧪 Testing:
- Vitest for unit testing
- Vitest UI for visual test feedback
- Test files colocated with source files
- ✅ Validation:
- Zod for runtime type validation and schema validation
- Validators colocated with features
- Automatic error handling for validation failures
src/
├── api/ # API routes and controllers
│ └── item/ # Item-related features
│ ├── item.controller.ts # Business logic
│ ├── item.model.ts # Data models
│ ├── item.router.ts # Route definitions
│ ├── item.validators.ts # Zod validation schemas
│ └── __tests__/
│ └── item.test.ts # Tests for item feature
├── config/ # Configuration files
├── testHelpers/ # Test utilities
└── app.ts # Application entry point
-
Install Dependencies
yarn install
-
Development Server
yarn dev
-
Testing
# Run tests in CLI yarn test # Run tests with UI yarn test:ui
-
Code Quality
# Lint yarn lint # Format yarn format # Type checking yarn typecheck
This project uses Zod for runtime type validation and schema validation. Validators are colocated with their respective features, following the project's colocation pattern.
Validators are defined in *.validators.ts files within each feature directory:
import { z } from 'zod'
export const createItemValidator = z.object({
name: z.string().min(1, { message: 'Name is required' })
})Validators are used in controllers to parse and validate request bodies:
import { createItemValidator } from './item.validators.ts'
export const createItem: RequestHandler = async (req, res, next) => {
try {
const { name } = createItemValidator.parse(req.body)
// Use validated data...
} catch (error) {
next(error) // Zod errors are automatically handled
}
}Zod validation errors are automatically handled by the error handler middleware. When validation fails, the API returns a 400 Bad Request response with detailed error information:
{
"message": "Validation failed",
"errors": [
{
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "Name is required",
"path": ["name"]
}
]
}The project uses path aliases for cleaner imports:
import { something } from '#src/something'
import { testHelper } from '#testHelpers/testHelper'
import { config } from '#config/config'- Create a feature branch
- Make your changes
- Run tests and linting
- Commit with a conventional commit message
- Push and create a pull request
MIT