10 Prompts for Node.js and Express: API, Middleware, and Authorization

Introduction

Node.js has become the backbone of modern backend development, powering everything from simple REST APIs to real-time collaboration tools. According to the 2025 Stack Overflow Developer Survey, Node.js remains the most used web framework among professional developers, with over 40% of respondents relying on it for server-side logic. For those building applications with Express—the most popular Node.js framework—crafting effective prompts can significantly accelerate development, from designing middleware to implementing secure authentication. In this article, we explore ten practical prompts that cover REST API creation, middleware patterns, and authorization flows, helping you write cleaner, more maintainable code.

Why Prompts Matter in Node.js Development

Prompts are structured instructions that guide AI tools or development workflows to generate code snippets, debug issues, or explain concepts. By using well-crafted prompts, you can:
- Reduce time spent on repetitive boilerplate (e.g., route handlers, validation)
- Improve code quality with consistent patterns
- Onboard new team members faster by providing clear examples

For Node.js and Express, prompts are especially valuable because the ecosystem is vast—there are dozens of middleware libraries, authentication strategies, and API design patterns. A good prompt can help you choose the right approach for your specific use case.

1. REST API Endpoint with Error Handling

Task: Create a RESTful endpoint that fetches a user by ID with proper error handling.

Prompt: "Generate an Express route handler for GET /users/:id that returns a user object from an in-memory array. Include validation for the ID parameter (must be a positive integer) and return appropriate HTTP status codes (200, 400, 404, 500). Use async/await and wrap the handler in a try-catch block."

Example Result:

const express = require('express');
const router = express.Router();

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

router.get('/users/:id', async (req, res) => {
  try {
    const id = parseInt(req.params.id, 10);
    if (isNaN(id) || id < 1) {
      return res.status(400).json({ error: 'Invalid user ID' });
    }
    const user = users.find(u => u.id === id);
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.status(200).json(user);
  } catch (err) {
    console.error('Error fetching user:', err);
    res.status(500).json({ error: 'Internal server error' });
  }
});

module.exports = router;

2. Custom Middleware for Logging

Task: Build a middleware function that logs each incoming request with timestamp, method, and URL.

Prompt: "Write an Express middleware that logs request details to the console in the format '[YYYY-MM-DD HH:mm:ss] METHOD /path'. Apply it globally before all routes. Ensure it does not block the request flow."

Example Result:

const morgan = require('morgan'); // However, we can write custom one

const requestLogger = (req, res, next) => {
  const timestamp = new Date().toISOString().replace('T', ' ').split('.')[0];
  console.log(`[${timestamp}] ${req.method} ${req.originalUrl}`);
  next();
};

app.use(requestLogger);

This pattern is similar to the popular morgan library but gives you full control. Production systems often extend this with structured logging (e.g., using pino or winston) to capture more context like response time and status codes.

3. JWT-Based Authentication Middleware

Task: Create a middleware that verifies a JSON Web Token (JWT) from the Authorization header and attaches the decoded user to the request object.

Prompt: "Implement an Express middleware function called authenticate that checks for a Bearer token in the Authorization header. Verify the token using a secret key (stored in environment variables). If valid, set req.user to the decoded payload. If invalid or missing, return 401 Unauthorized. Use the jsonwebtoken library."

Example Result:

const jwt = require('jsonwebtoken');

const authenticate = (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, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
};

// Usage in protected routes
app.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});

4. Role-Based Access Control (RBAC) Middleware

Task: Extend authentication with role-based checks for admin-only endpoints.

Prompt: "Write a middleware factory authorize(roles) that returns a middleware function. It should check if req.user.role is included in the allowed roles array. If not, return 403 Forbidden. Assume req.user is already populated by an authentication middleware."

Example Result:

const authorize = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user || !allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
};

// Usage
app.delete('/admin/users/:id', authenticate, authorize('admin'), async (req, res) => {
  // delete user logic
});

5. Input Validation with Express Validator

Task: Validate request body fields for a user registration endpoint.

Prompt: "Create an Express route for POST /register that validates: email (must be a valid email format), password (minimum 8 characters, at least one number), and name (required, max 50 chars). Use express-validator to check these rules and return a 422 status with error messages if validation fails."

Example Result:

const { body, validationResult } = require('express-validator');

app.post('/register', [
  body('email').isEmail().withMessage('Must be a valid email'),
  body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 chars')
    .matches(/\d/).withMessage('Password must contain a number'),
  body('name').notEmpty().isLength({ max: 50 }).withMessage('Name is required (max 50 chars)')
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(422).json({ errors: errors.array() });
  }
  // proceed with registration
});

6. Rate Limiting Middleware

Task: Protect your API from abuse by limiting requests per IP.

Prompt: "Implement a simple rate limiter middleware that allows a maximum of 100 requests per minute per IP address. Use an in-memory store (a Map) to track request counts. If the limit is exceeded, return 429 Too Many Requests with a Retry-After header."

Example Result:

const requestCounts = new Map();
const WINDOW_MS = 60 * 1000;
const MAX_REQUESTS = 100;

const rateLimiter = (req, res, next) => {
  const ip = req.ip;
  const now = Date.now();
  if (!requestCounts.has(ip)) {
    requestCounts.set(ip, []);
  }
  const timestamps = requestCounts.get(ip).filter(t => now - t < WINDOW_MS);
  if (timestamps.length >= MAX_REQUESTS) {
    res.set('Retry-After', Math.ceil(WINDOW_MS / 1000));
    return res.status(429).json({ error: 'Too many requests' });
  }
  timestamps.push(now);
  requestCounts.set(ip, timestamps);
  next();
};

app.use(rateLimiter);

For production, consider using express-rate-limit which handles distributed environments better.

7. WebSocket Integration with Socket.IO

Task: Set up a real-time chat feature using WebSockets alongside Express.

Prompt: "Create a Socket.IO server integrated with Express. On connection, log the user's socket ID. Implement an event 'chat message' that broadcasts the message to all connected clients. Also handle disconnection."

Example Result:

const http = require('http');
const socketIo = require('socket.io');

const server = http.createServer(app);
const io = socketIo(server, {
  cors: { origin: '*' }
});

io.on('connection', (socket) => {
  console.log('User connected:', socket.id);

  socket.on('chat message', (msg) => {
    io.emit('chat message', { user: socket.id, text: msg });
  });

  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

server.listen(3000, () => console.log('Server running on port 3000'));

8. CRUD with MongoDB and Mongoose

Task: Build a complete CRUD API for a 'Product' resource using Mongoose.

Prompt: "Define a Mongoose schema for Product with fields: name (String, required), price (Number, required, min 0), category (String, enum ['Electronics', 'Clothing', 'Food']). Create Express routes for GET /products (list all), GET /products/:id, POST /products, PUT /products/:id, DELETE /products/:id. Use async/await and proper error handling."

Example Result:

const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  name: { type: String, required: true },
  price: { type: Number, required: true, min: 0 },
  category: { type: String, enum: ['Electronics', 'Clothing', 'Food'] }
});

const Product = mongoose.model('Product', productSchema);

// GET all products
app.get('/products', async (req, res) => {
  try {
    const products = await Product.find();
    res.json(products);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// POST create product
app.post('/products', async (req, res) => {
  try {
    const product = new Product(req.body);
    await product.save();
    res.status(201).json(product);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Similar for GET/:id, PUT, DELETE...

9. Error-Handling Middleware

Task: Create a centralized error handler that catches all unhandled errors and returns a consistent JSON response.

Prompt: "Write a global error-handling middleware for Express that accepts an error object. Log the error stack trace in development mode. Return a JSON response with status code (default 500), message, and in development also include the stack trace. Use app.use() at the end of the middleware stack."

Example Result:

const errorHandler = (err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Internal server error';

  if (process.env.NODE_ENV === 'development') {
    console.error(err.stack);
  }

  res.status(statusCode).json({
    error: message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

app.use(errorHandler);

10. Environment Configuration with dotenv

Task: Manage configuration variables securely.

Prompt: "Create a config module that loads environment variables from a .env file using dotenv. Export an object with properties like PORT, DB_URI, JWT_SECRET. Provide default values where applicable."

Example Result:

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',
  nodeEnv: process.env.NODE_ENV || 'development'
};

module.exports = config;

// Usage in app
const app = express();
app.listen(config.port, () => {
  console.log(`Server running in ${config.nodeEnv} mode on port ${config.port}`);
});

Conclusion

These ten prompts cover the essential building blocks of any Node.js and Express application: from basic routing and middleware to authentication, validation, and real-time communication. By using these as templates, you can save hours of boilerplate coding and focus on your unique business logic. Remember to adapt each snippet to your specific use case—for example, replace the in-memory user store with a real database like MongoDB or PostgreSQL. The Express ecosystem is mature and well-documented; the official Express documentation at expressjs.com and the Node.js docs at nodejs.org are excellent resources for deeper dives. For those looking to build production-grade APIs, consider combining these patterns with testing frameworks like Jest and Mocha, and deployment tools like Docker and CI/CD pipelines. Happy coding!

← All posts

Comments