This guide will help you get up and running with the Node.js API boilerplate in minutes.
Before you begin, ensure you have the following installed:
- Node.js >= 24.12.0 (Download or use nvm)
- npm >= 10.0.0
- PostgreSQL >= 14 (Download)
- Docker (optional, for Temporal Server) (Download)
- Git (Download)
git clone https://github.com/darshitvvora/node-temporal-postgres-boilerplate.git
cd node-temporal-postgres-boilerplatenpm install# Copy sample environment file
cp sample.env .env
# Edit .env file with your configuration
nano .env # or use your favorite editorRequired environment variables:
# Application
NODE_ENV=development
PORT=3015
# Database
DB_HOST=localhost
DB_NAME=node_api_db
DB_USER=postgres
DB_PASSWORD=your_password
DB_PORT=5432
# Temporal
TEMPORAL_ADDRESS=localhost:7233
# Logging
LOG_LEVEL=debug# Create database
createdb node_api_db
# Run migrations
npm run migrate# Using Docker (recommended)
docker run -d \
-p 7233:7233 \
-p 8233:8233 \
--name temporal-server \
temporalio/auto-setup:latest
# Verify it's running
docker ps | grep temporalAccess Temporal Web UI at: http://localhost:8233
Open 3 terminal windows:
Terminal 1 - API Server:
npm startTerminal 2 - Temporal Worker:
npm run start:worker:userTerminal 3 - Test the API:
# Health check
curl http://localhost:3015/health
# Create a user
curl -X POST http://localhost:3015/api/users \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"mobile": "1234567890"
}'- API Docs (Swagger): http://localhost:3015/api-docs
- Temporal UI: http://localhost:8233
🎉 Congratulations! Your API is now running with Temporal workflows.
node-api-boilerplate/
├── src/
│ ├── api/ # API endpoints
│ │ └── user/
│ │ ├── user.routes.js # Route definitions
│ │ ├── user.controller.js # Request handlers
│ │ ├── user.property.js # Model schema
│ │ └── user.hookshot.js # Event handlers
│ ├── config/ # Configuration files
│ │ ├── environment/ # Environment configs
│ │ ├── express.js # Express setup
│ │ └── swagger.js # API documentation
│ ├── db/ # Database
│ │ ├── models/ # Sequelize models
│ │ └── migrations/ # Database migrations
│ ├── temporal/ # Temporal workflows
│ │ ├── activities/ # Business logic
│ │ ├── workflows/ # Workflow definitions
│ │ ├── clients/ # Workflow clients
│ │ └── workers/ # Worker processes
│ ├── middleware/ # Express middleware
│ ├── utils/ # Utility functions
│ ├── routes.js # Main router
│ └── server.js # Application entry point
├── tests/ # Test files
├── wiki/ # Detailed guides
├── docker-compose.yml # Docker setup
├── k8s-deployment.yaml # Kubernetes manifests
└── package.json # Dependencies
File: src/api/resource/resource.routes.js
import { Router } from 'express';
import * as controller from './resource.controller.js';
const router = Router();
router.post('/', controller.create);
router.get('/', controller.index);
router.get('/:id', controller.show);
router.put('/:id', controller.update);
router.delete('/:id', controller.destroy);
export default router;File: src/api/resource/resource.controller.js
import * as resourceClient from '../../temporal/clients/resource.client.js';
/**
* @openapi
* /api/resources:
* post:
* summary: Create a new resource
* tags:
* - Resources
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* responses:
* 201:
* description: Resource created successfully
*/
export async function create(req, res, next) {
try {
const result = await resourceClient.startCreateResource(req.body);
return res.status(result.code).json(result);
} catch (error) {
next(error);
}
}
export async function index(req, res, next) {
try {
const result = await resourceClient.startGetAllResources(req.query);
return res.status(result.code).json(result);
} catch (error) {
next(error);
}
}File: src/temporal/activities/resource/activities.js
import db from '../../../db/models/index.js';
const { Resource } = db;
export async function createResourceActivity(data) {
const resource = await Resource.create(data);
return { id: resource.id, success: true };
}
export async function getAllResourcesActivity({ limit = 100, offset = 0 }) {
const resources = await Resource.findAll({ limit, offset });
return resources;
}File: src/temporal/workflows/resource/createResource.workflow.js
import { proxyActivities } from '@temporalio/workflow';
const activities = proxyActivities({
startToCloseTimeout: '1 minute',
});
async function createResourceWorkflow(data) {
try {
const result = await activities.createResourceActivity(data);
return { code: 201, success: true, ...result };
} catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') {
return { code: 409, message: 'Resource already exists' };
}
throw error;
}
}
export { createResourceWorkflow };File: src/temporal/clients/resource.client.js
import { getClient } from '../config/temporal.js';
const RESOURCE_QUEUE = 'resource';
async function startCreateResource(data) {
const client = await getClient();
const workflowId = `create-resource-${Date.now()}`;
const handle = await client.workflow.start('createResourceWorkflow', {
args: [data],
taskQueue: RESOURCE_QUEUE,
workflowId,
});
return handle.result();
}
export { startCreateResource };File: src/routes.js
import resourceRoutes from './api/resource/resource.routes.js';
export default function (app) {
app.use('/api/users', userRoutes);
app.use('/api/resources', resourceRoutes); // Add this
}# Create a resource
curl -X POST http://localhost:3015/api/resources \
-H "Content-Type: application/json" \
-d '{"name":"My Resource"}'
# Get all resources
curl http://localhost:3015/api/resources
# View in Swagger
open http://localhost:3015/api-docs# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run specific test file
npm test -- tests/integration/user.test.js# Lint code
npm run lint
# Fix linting issues
npm run lint:fix
# Format code
npm run pretty# Create new migration
npx sequelize-cli migration:generate --name create-resources
# Run migrations
npm run migrate
# Rollback last migration
npx sequelize-cli db:migrate:undo
# Reset database
npm run clear-db
npm run migrate# Application logs are stored in logs/
tail -f logs/app.log
# Error logs
tail -f logs/error.log
# Temporal worker logs
npm run start:worker:user # logs to console# Start in debug mode
npm run debug
# Attach debugger at http://localhost:9229// Add breakpoints in worker code
// Run worker with:
node --inspect ./src/temporal/workers/user.worker.js
// Attach Chrome DevTools
// Open chrome://inspect in Chrome browserError: connect ECONNREFUSED 127.0.0.1:5432Solution: Ensure PostgreSQL is running
# macOS
brew services start postgresql
# Linux
sudo systemctl start postgresql
# Check status
psql -U postgres -c "SELECT version();"Error: Connection refused to Temporal ServerSolution: Start Temporal Server
docker run -d -p 7233:7233 -p 8233:8233 temporalio/auto-setup:latestError: listen EADDRINUSE: address already in use :::3015Solution: Kill process or change port
# Find process
lsof -i :3015
# Kill process
kill -9 <PID>
# Or change PORT in .env fileSolution: Ensure worker is running
# Check if worker is running
ps aux | grep worker
# Restart worker
npm run start:worker:userNow that you're up and running:
- Explore the Code: Browse
src/api/user/to see a complete example - Read the Guides:
- Temporal Workflows Guide - Learn about workflows
- API Documentation Guide - API docs standards
- Deployment Guide - Deploy to production
- Try Examples: Test the user API endpoints in Swagger UI
- Watch Workflows: Monitor executions in Temporal Web UI
- Add Your Features: Use the examples to add your own endpoints
- GitHub Issues: Report bugs or request features
- Documentation: Check
wiki/folder for detailed guides - Temporal Community: Temporal Forum
- Stack Overflow: Tag your questions with
temporalioandnode.js
- Use Nodemon: The dev server auto-restarts on file changes
- Check Temporal UI: Always monitor workflow executions at http://localhost:8233
- Read Logs: Check
logs/directory for detailed application logs - Use Swagger: Test APIs interactively at http://localhost:3015/api-docs
- Commit Often: Use conventional commits with Commitizen (
git cz)
Happy coding! 🚀