Introduction
Building robust Node.js applications with Express requires more than just knowing the syntax. You need a systematic approach to handle common tasks like creating REST APIs, implementing authentication, managing middleware, and adding real-time features with WebSockets. This collection of 15 battle-tested prompts will accelerate your development workflow. Each prompt includes a clear description, a practical use case, and a code example you can adapt immediately.
These prompts are designed for developers who use AI tools daily to speed up coding. They cover the most frequent scenarios in Express-based projects, from setting up a basic API server to securing routes with JWT and handling WebSocket connections. By using these prompts, you can reduce boilerplate code, avoid common pitfalls, and focus on business logic.
1. Generate a Basic Express Server with Routes
Prompt: Create an Express server that listens on port 3000, with a root route (GET /) returning JSON {"message": "Hello World"}, a health check route (GET /health) returning {"status": "ok"}, and a 404 handler for undefined routes.
Use case: This is the starting point for any Express project. Instead of manually writing the same boilerplate, use this prompt to get a ready-to-run server.
Code:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello World' });
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
2. Build a RESTful API for a Resource (e.g., Users)
Prompt: Implement CRUD endpoints for a User resource: GET /users (list all), GET /users/:id (get one), POST /users (create), PUT /users/:id (update), DELETE /users/:id (delete). Use an in-memory array as the data store. Return appropriate HTTP status codes (200, 201, 404, 500) and JSON responses.
Use case: Essential for any API project. This prompt gives you a complete CRUD pattern that you can later replace with a database.
Code:
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
let nextId = 3;
app.get('/users', (req, res) => {
res.json(users);
});
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
app.post('/users', (req, res) => {
const { name } = req.body;
if (!name) return res.status(400).json({ error: 'Name is required' });
const newUser = { id: nextId++, name };
users.push(newUser);
res.status(201).json(newUser);
});
app.put('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'User not found' });
const { name } = req.body;
if (!name) return res.status(400).json({ error: 'Name is required' });
user.name = name;
res.json(user);
});
app.delete('/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: 'User not found' });
users.splice(index, 1);
res.status(200).json({ message: 'Deleted' });
});
3. Implement JWT Authentication Middleware
Prompt: Create middleware that verifies a JWT token from the Authorization header (Bearer scheme). If the token is valid, attach the decoded payload to req.user. If invalid or missing, return 401 Unauthorized. Use the jsonwebtoken library. Also provide a login route that returns a token.
Use case: Protect your API routes with token-based authentication. This is a standard pattern for modern web apps.
Code:
const jwt = require('jsonwebtoken');
const SECRET = 'your-secret-key';
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
}
app.post('/login', (req, res) => {
const { username, password } = req.body;
// In real app, validate credentials against DB
if (username === 'admin' && password === 'password') {
const token = jwt.sign({ username, role: 'admin' }, SECRET, { expiresIn: '1h' });
return res.json({ token });
}
res.status(401).json({ error: 'Invalid credentials' });
});
4. Create Role-Based Access Control (RBAC) Middleware
Prompt: Build middleware that checks if the authenticated user has the required role (e.g., 'admin', 'user'). Accept an array of allowed roles. Use it in combination with JWT middleware. Return 403 Forbidden if the role is insufficient.
Use case: Restrict access to certain endpoints based on user roles. For example, only admins can delete users.
Code:
function requireRole(...roles) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden: insufficient permissions' });
}
next();
};
}
app.delete('/users/:id', authMiddleware, requireRole('admin'), (req, res) => {
// delete user logic
});
5. Add Request Logging Middleware
Prompt: Write a middleware that logs each incoming request: method, URL, timestamp, and response status code. Use console.log for simplicity. Attach the log to the response's finish event.
Use case: Debugging and monitoring your API during development. This gives you a simple audit trail.
Code:
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${new Date().toISOString()}
| ${req.method} ${req.url} | ${res.statusCode} | ${duration}ms`);
});
next();
});
6. Handle CORS for Your Express API
Prompt: Configure CORS (Cross-Origin Resource Sharing) to allow requests from a specific origin (e.g., http://localhost:5173 for a Vite frontend). Allow methods GET, POST, PUT, DELETE and headers Content-Type, Authorization.
Use case: Essential when your frontend runs on a different port or domain. Without CORS, browsers block cross-origin requests.
Code:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:5173');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
7. Implement Error Handling Middleware
Prompt: Create a global error-handling middleware that catches errors thrown in route handlers. Log the error, and return a JSON response with status 500 and a generic error message. Include a custom error class for application-specific errors.
Use case: Prevent server crashes and provide consistent error responses. This is a best practice for production apps.
Code:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
function errorHandler(err, req, res, next) {
console.error(err.stack);
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: err.message || 'Internal Server Error'
});
}
app.get('/users/:id', (req, res, next) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return next(new AppError('User not found', 404));
res.json(user);
});
app.use(errorHandler);
8. Build a File Upload Endpoint with Multer
Prompt: Create a POST /upload route that accepts a single file upload (field name 'avatar'). Save it to the uploads/ directory. Validate that the file is an image (MIME type starting with 'image/'). Return the file path. Use the multer library.
Use case: Handle user profile pictures, document uploads, or any file input in your API.
Code:
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: './uploads/',
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname));
}
});
const upload = multer({
storage,
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only images allowed'), false);
}
}
});
app.post('/upload', upload.single('avatar'), (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
res.json({ path: req.file.path });
});
9. Connect to MongoDB and Create a Model
Prompt: Set up a connection to MongoDB using Mongoose. Define a User model with fields: name (String, required), email (String, required, unique), password (String, required), and createdAt (Date, default now). Create a route to register a new user (POST /register) that hashes the password with bcrypt before saving.
Use case: Persist user data in a database. This is the foundation for any data-driven application.
Code:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true, useUnifiedTopology: true });
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const bcrypt = require('bcrypt');
this.password = await bcrypt.hash(this.password, 10);
next();
});
const User = mongoose.model('User', userSchema);
app.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
const user = new User({ name, email, password });
await user.save();
res.status(201).json({ message: 'User created' });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
10. Implement Rate Limiting with express-rate-limit
Prompt: Add rate limiting to your Express app. Limit each IP to 100 requests per 15 minutes. Return a 429 Too Many Requests status with a JSON error message when the limit is exceeded. Use the express-rate-limit package.
Use case: Protect your API from abuse and brute-force attacks. This is a standard security measure.
Code:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: { error: 'Too many requests, please try again later.' },
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
11. Create a WebSocket Server with Socket.IO
Prompt: Set up a WebSocket server using Socket.IO alongside Express. On connection, log the user. Listen for a 'message' event from the client and broadcast it to all connected clients. Also handle disconnection.
Use case: Add real-time features like chat, notifications, or live updates to your app.
Code:
const http = require('http');
const { Server } = require('socket.io');
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: 'http://localhost:5173' }
});
io.on('connection', (socket) => {
console.log(`User connected: ${socket.id}`);
socket.on('message', (data) => {
console.log('Message received:', data);
io.emit('message', data); // broadcast to all
});
socket.on('disconnect', () => {
console.log(`User disconnected: ${socket.id}`);
});
});
server.listen(3000, () => {
console.log('Server with WebSocket running on port 3000');
});
12. Validate Input Data with Joi
Prompt: Use the Joi library to create a validation schema for a user registration endpoint. Validate that name is a string (min 2 chars), email is a valid email, and password is a string (min 6 chars, must contain a number). Return 400 with detailed error messages if validation fails.
Use case: Ensure data integrity and provide clear feedback to API consumers.
Code:
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).pattern(/\d/).required().messages({
'string.pattern.base': 'Password must contain at least one number'
})
});
app.post('/register', (req, res, next) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
// proceed with registration
res.json({ message: 'Validation passed' });
});
13. Build a Paginated API Endpoint
Prompt: Create a GET /items endpoint that accepts query parameters page (default 1) and limit (default 10). Return a JSON object with data (the items for the current page), total (total number of items), page, and totalPages. Use an in-memory array of 100 items for demonstration.
Use case: Efficiently handle large datasets by splitting results into pages. This improves performance and UX.
Code:
const items = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, name: `Item ${i + 1}` }));
app.get('/items', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const data = items.slice(startIndex, endIndex);
res.json({
data,
total: items.length,
page,
totalPages: Math.ceil(items.length / limit)
});
});
14. Add Environment Variable Configuration
Prompt: Use the dotenv package to load environment variables from a .env file. Set up a configuration object that reads PORT, DB_URI, JWT_SECRET, and CORS_ORIGIN. Use defaults where appropriate. Demonstrate how to access these values in the server setup.
Use case: Keep sensitive data like API keys and database URIs out of your code. This is a security best practice.
Code:
require('dotenv').config();
const config = {
port: process.env.PORT || 3000,
dbUri: process.env.DB_URI || 'mongodb://localhost:27017/myapp',
jwtSecret: process.env.JWT_SECRET || 'fallback-secret',
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:5173'
};
console.log(`Server starting on port ${config.port}`);
// Use config values throughout the app
15. Set Up a WebSocket Chat with Authentication
Prompt: Extend the Socket.IO server to authenticate WebSocket connections using a JWT token passed as a query parameter. On connection, verify the token and store user info in the socket object. Allow authenticated users to join rooms and send messages to specific rooms.
Use case: Build a secure chat application where only authenticated users can send and receive messages.
Code:
const jwt = require('jsonwebtoken');
io.use((socket, next) => {
const token = socket.handshake.query.token;
if (!token) return next(new Error('Authentication error'));
try {
const decoded = jwt.verify(token, config.jwtSecret);
socket.user = decoded;
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
console.log(`Authenticated user: ${socket.user.username}`);
socket.on('join-room', (room) => {
socket.join(room);
socket.to(room).emit('message', { user: 'System', text: `${socket.user.username} joined` });
});
socket.on('room-message', ({ room, text }) => {
io.to(room).emit('message', { user: socket.user.username, text });
});
});
Conclusion
These 15 prompts cover the essential patterns for building production-ready Node.js applications with Express. From basic server setup to advanced features like WebSocket authentication and rate limiting, each prompt is designed to save you time and reduce errors. Start by copying the code snippets and adapt them to your specific needs.
For further learning, refer to the official Express documentation at expressjs.com and the Socket.IO documentation at socket.io. The Node.js documentation at nodejs.org also provides detailed guides on streams, buffers, and other core modules.
Now, pick a prompt, paste it into your AI tool, and start building. Your next API project will be up and running in minutes.
Comments