Interactive API docs powered by Swagger UI.
Access: http://localhost:3000/docs
The OpenAPI documentation is organized in a modular structure:
src/docs/
├── openapi.ts # Main file (merges all paths)
├── schemas/ # Reusable schemas
│ ├── common.schema.ts # Common responses (errors, etc.)
│ └── task.schema.ts # Task-related schemas
└── paths/ # Endpoint definitions
├── data.paths.ts # /api/v1/data endpoints
└── tasks.paths.ts # /api/v1/tasks endpoints
Step 1: Define schemas (if needed)
Create or update schema files in src/docs/schemas/:
// src/docs/schemas/user.schema.ts
import type { OpenAPIV3 } from 'openapi-types';
export const userSchema: OpenAPIV3.SchemaObject = {
type: 'object',
properties: {
id: { type: 'integer', example: 1 },
name: { type: 'string', example: 'John Doe' },
email: { type: 'string', example: 'john@example.com' }
}
};
export const createUserSchema: OpenAPIV3.SchemaObject = {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', example: 'John Doe' },
email: { type: 'string', example: 'john@example.com' }
}
};Step 2: Create path definitions
Create a new file in src/docs/paths/:
// src/docs/paths/users.paths.ts
import type { OpenAPIV3 } from 'openapi-types';
import { userSchema, createUserSchema } from '../schemas/user.schema.js';
import { commonResponses } from '../schemas/common.schema.js';
export const usersPaths: OpenAPIV3.PathsObject = {
'/api/v1/users': {
get: {
summary: 'List all users',
tags: ['Users'],
responses: {
'200': {
description: 'Success',
content: {
'application/json': {
schema: {
type: 'array',
items: userSchema
}
}
}
},
'500': commonResponses[500]
}
},
post: {
summary: 'Create a new user',
tags: ['Users'],
requestBody: {
required: true,
content: {
'application/json': {
schema: createUserSchema
}
}
},
responses: {
'201': {
description: 'User created',
content: {
'application/json': {
schema: userSchema
}
}
},
'400': commonResponses[400],
'500': commonResponses[500]
}
}
}
};Step 3: Import and merge paths
Update src/docs/openapi.ts:
import { usersPaths } from './paths/users.paths.js';
export const openApiSpec: OpenAPIV3.Document = {
// ... existing config
paths: {
...dataPaths,
...tasksPaths,
...usersPaths // Add your new paths
}
};Step 4: View updated docs
Start the server and visit http://localhost:3000/docs to see your new endpoints.
- Organized: Each resource in its own file
- Reusable: Define schemas once, use everywhere
- Scalable: Easy to add endpoints without cluttering
- Type-safe: Full TypeScript support
| Tool | Purpose |
|---|---|
| ESLint | Catches bugs, enforces best practices, prevents bad patterns |
| Prettier | Auto-formats code for consistent style |
# Check for issues
npm run lint
# Auto-fix issues
npm run lint:fix
# Format code
npm run format
# Check formatting without changes
npm run format:check- Before committing: Run
npm run lint:fix && npm run format - During development: IDE extensions auto-fix on save
- In CI/CD: Run
npm run lint && npm run format:checkto block bad code
Install extensions:
Add to .vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}Testing is powered by Jest with TypeScript support via ts-jest. Tests follow the Arrange, Act, Assert (AAA) pattern.
| Test Type | Location | Purpose |
|---|---|---|
| Unit | tests/unit/ |
Test individual functions/modules in isolation |
| Integration | tests/integration/ |
Test multiple components working together |
# Run all tests
npm test
# Run tests in watch mode (re-runs on file changes)
npm run test:watch
# Run tests with coverage report
npm run test:coverage
# Run only unit tests
npm run test:unit
# Run only integration tests
npm run test:integrationMirror the src/ structure inside tests/:
tests/
├── unit/
│ └── shared/utils/
│ └── dateHelper.test.ts # Tests for src/shared/utils/dateHelper.ts
└── integration/
└── *.test.ts
Focus on tests that have high impact on behavior. Don't write tests for everything — write them where they matter.
Write unit tests when you add:
| Scenario | Example |
|---|---|
| Input validation logic | Validating task title is not empty, date format, etc. |
| Data transformation | Converting dates, formatting responses, mapping data |
| Business rules | Overdue detection, task status transitions |
| Utility functions | Helpers with logic that could break |
Write integration tests when you add:
| Scenario | Example |
|---|---|
| API endpoints | POST /tasks returns 201 with valid data, 400 without |
| Database operations | Task CRUD operations persist and retrieve correctly |
| Multi-step workflows | Creating a task then moving it between lists |
Skip tests for:
- Config/setup files (
prisma.ts,server.ts) - Static definitions (OpenAPI schemas, types)
- Simple pass-through code with no logic
Create test files with .test.ts extension using the AAA pattern:
import { describe, it, expect } from '@jest/globals';
describe('validateTaskInput', () => {
// Test Case: valid input
it('should accept a task with a title', () => {
// Arrange
const input = { title: 'Buy groceries' };
// Act
const result = validateTaskInput(input);
// Assert
expect(result.isValid).toBe(true);
});
// Test Case: invalid input
it('should reject a task without a title', () => {
// Arrange
const input = { title: '' };
// Act
const result = validateTaskInput(input);
// Assert
expect(result.isValid).toBe(false);
expect(result.error).toBe('Title is required');
});
});After running npm run test:coverage, view the report:
- Terminal: Summary displayed after tests
- HTML: Open
coverage/index.htmlin browser - CI/CD: Use
coverage/lcov.infofor coverage tools
Automated checks run on every push and pull request to main and develop branches.
| Step | What it does |
|---|---|
| Lint | Runs ESLint to catch code issues |
| Format | Checks Prettier formatting |
| Type check | Validates TypeScript types |
| Test | Runs all Jest tests |
-
File location:
.github/workflows/ci.yml(already created) -
Push to GitHub:
git add .github/workflows/ci.yml git commit -m "chore(ci): add GitHub Actions workflow" git push -
View results: Go to your GitHub repo → Actions tab
-
(Optional) Enable branch protection:
- Go to repo Settings → Branches
- Click Add rule for
main - Enable Require status checks to pass before merging
- Select
build-and-testfrom the list
Add to the top of your README:
Format: type(scope): description
| Type | When to Use |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no code change |
refactor |
Code restructure, no behavior change |
test |
Adding/updating tests |
chore |
Build, config, dependencies |
feat(auth): add login endpoint
fix(api): handle null response
docs(readme): update setup instructions
refactor(users): extract validation logic
chore(deps): upgrade express to v5- Use lowercase
- No period at end
- Keep under 72 characters
- Use imperative mood ("add" not "added")
Format: type/short-description
| Prefix | Purpose |
|---|---|
main |
Production-ready code |
develop |
Integration branch |
feature/ |
New features |
fix/ |
Bug fixes |
hotfix/ |
Urgent production fixes |
release/ |
Release preparation |
feature/user-authentication
feature/add-payment-api
fix/login-validation-error
hotfix/security-patch
release/v1.2.0# Create feature branch
git checkout -b feature/add-login develop
# Work on feature...
git add .
git commit -m "feat(auth): add login endpoint"
# Push and create PR
git push -u origin feature/add-login
# Go back to main branch
git checkout main
# Get update remote repo
git pull
# After PR merged, delete branch
git branch -d feature/add-login- Branch from
developfor features/fixes - Branch from
mainfor hotfixes - Keep branch names short and descriptive
- Delete branches after merging