Skip to content

Express Examples

Practical examples for using Response Handler with Express.

Basic REST API

typescript
import express from 'express';
import { quickSetup } from '@amit-kandar/response-handler';

const app = express();
app.use(express.json());

const { middleware, errorHandler } = quickSetup({
  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
  logging: { enabled: true },
});

app.use(middleware);

// Users API
app.get('/api/users', async (req, res) => {
  const users = await User.findAll();
  res.ok(users, 'Users retrieved successfully');
});

app.get('/api/users/:id', async (req, res) => {
  const user = await User.findById(req.params.id);

  if (!user) {
    return res.notFound({ id: req.params.id }, 'User not found');
  }

  res.ok(user, 'User retrieved successfully');
});

app.post('/api/users', async (req, res) => {
  const { name, email } = req.body;

  // Validation
  if (!name || !email) {
    return res.badRequest(
      { missingFields: ['name', 'email'].filter((f) => !req.body[f]) },
      'Missing required fields',
    );
  }

  try {
    const user = await User.create({ name, email });
    res.created(user, 'User created successfully');
  } catch (error) {
    if (error.code === 'DUPLICATE_EMAIL') {
      return res.conflict({ email }, 'Email already exists');
    }
    throw error; // Will be caught by error handler
  }
});

app.use(errorHandler);
app.listen(3000);

Authentication & Authorization

typescript
// Authentication middleware
app.use('/api/protected', (req, res, next) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return res.unauthorized(null, 'Authentication token required');
  }

  try {
    const user = jwt.verify(token, SECRET);
    req.user = user;
    next();
  } catch (error) {
    res.unauthorized({ reason: 'Invalid token' }, 'Invalid authentication token');
  }
});

// Authorization middleware
const requireRole = (role) => (req, res, next) => {
  if (req.user.role !== role) {
    return res.forbidden({ required: role, actual: req.user.role }, `${role} access required`);
  }
  next();
};

app.get('/api/protected/admin', requireRole('admin'), (req, res) => {
  res.ok({ message: 'Admin area accessed' });
});

Validation with Joi

typescript
import Joi from 'joi';

const validateBody = (schema) => (req, res, next) => {
  const { error, value } = schema.validate(req.body);

  if (error) {
    return res.badRequest(
      {
        details: error.details.map((d) => ({
          field: d.path.join('.'),
          message: d.message,
        })),
      },
      'Validation failed',
    );
  }

  req.body = value;
  next();
};

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  age: Joi.number().min(18).max(100),
});

app.post('/api/users', validateBody(userSchema), async (req, res) => {
  const user = await User.create(req.body);
  res.created(user, 'User created successfully');
});

Pagination

typescript
app.get('/api/posts', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const offset = (page - 1) * limit;

  const { posts, total } = await Post.findAndCountAll({
    limit,
    offset,
    order: [['createdAt', 'DESC']],
  });

  const pagination = {
    page,
    limit,
    total,
    totalPages: Math.ceil(total / limit),
    hasNext: page < Math.ceil(total / limit),
    hasPrev: page > 1,
  };

  res.paginate(posts, pagination, 'Posts retrieved successfully');
});

File Upload & Download

typescript
import multer from 'multer';

const upload = multer({ dest: 'uploads/' });

app.post('/api/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.badRequest({ field: 'file' }, 'File is required');
  }

  const fileInfo = {
    id: req.file.filename,
    originalName: req.file.originalname,
    size: req.file.size,
    uploadedAt: new Date().toISOString(),
  };

  res.created(fileInfo, 'File uploaded successfully');
});

app.get('/api/download/:id', (req, res) => {
  const filePath = path.join('uploads', req.params.id);

  if (!fs.existsSync(filePath)) {
    return res.notFound({ id: req.params.id }, 'File not found');
  }

  res.downloadFile(filePath, 'document.pdf');
});

Rate Limiting

typescript
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  handler: (req, res) => {
    res.tooManyRequests(
      {
        limit: 100,
        windowMs: 15 * 60 * 1000,
        retryAfter: Math.round(req.rateLimit.resetTime / 1000),
      },
      'Too many requests, please try again later',
    );
  },
});

app.use('/api/', limiter);

Error Handling

typescript
// Custom error types
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
    this.statusCode = 400;
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} not found`);
    this.name = 'NotFoundError';
    this.resource = resource;
    this.id = id;
    this.statusCode = 404;
  }
}

// Service layer
class UserService {
  static async findById(id) {
    const user = await User.findById(id);
    if (!user) {
      throw new NotFoundError('User', id);
    }
    return user;
  }

  static async create(userData) {
    if (!userData.email) {
      throw new ValidationError('Email is required', 'email');
    }

    return await User.create(userData);
  }
}

// Controller
app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await UserService.findById(req.params.id);
    res.ok(user, 'User retrieved successfully');
  } catch (error) {
    next(error); // Will be handled by error handler
  }
});

Testing

typescript
import request from 'supertest';
import { quickSetup } from '@amit-kandar/response-handler';

describe('Users API', () => {
  let app;

  beforeEach(() => {
    app = express();
    const { middleware, errorHandler } = quickSetup({ mode: 'test' });
    app.use(middleware);
    // ... setup routes
    app.use(errorHandler);
  });

  it('should create user successfully', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'John', email: 'john@example.com' })
      .expect(201);

    expect(response.body).toMatchObject({
      success: true,
      data: expect.objectContaining({
        name: 'John',
        email: 'john@example.com',
      }),
      message: 'User created successfully',
    });
  });

  it('should handle validation errors', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'John' }) // Missing email
      .expect(400);

    expect(response.body).toMatchObject({
      success: false,
      message: expect.stringContaining('Missing required fields'),
      error: expect.any(Object),
    });
  });
});

Released under the ISC License.