Skip to main content

API Structure & Middleware

Bike4Mind’s API is structured for consistency, security, and maintainability. It is built using Next.js API routes and wraps every endpoint with a common middleware layer that handles cross-cutting concerns such as authentication, error handling, logging, and permission evaluation.

API Design Philosophy

  • All API routes are explicitly defined and colocated with business logic
  • Routes follow REST-like semantics with clear resource boundaries (e.g., /api/notebooks, /api/files)
  • Middleware ensures that shared concerns are handled uniformly across endpoints
  • Developer ergonomics are prioritized: middleware and utilities abstract boilerplate without reducing clarity

baseApi() Middleware

All API routes are wrapped in a custom baseApi() function, which provides a standard pipeline for handling requests. This wrapper includes the following layers:

  1. Database Connection Each request initializes a connection to the primary MongoDB instance using pooled, connection-aware logic.

  2. Authentication The middleware verifies JWT tokens, extracts the authenticated user, and attaches it to the request object (req.user).

  3. Authorization Context The user’s permission context is evaluated and an Ability object is generated via CASL. This is attached to the request as req.ability.

  4. Error Handling Errors thrown within routes are caught and passed through a standardized error handler that returns structured, non-leaky responses.

  5. Request Logging Key request metadata (endpoint, user, status, timing) is logged for auditing and observability purposes.

This middleware provides the necessary context and protection mechanisms to every route without repetitive implementation.

Route Pattern and Composition

API handlers are composed using chained functions, for example:

const handler = baseApi().post(
asyncHandler(async (req, res) => {
if (!req.ability.can('create', 'Notebook')) {
throw new ForbiddenError('Insufficient permissions');
}

const data = validateNotebookData(req.body);
const notebook = await createNotebook({ ...data, userId: req.user.id });

res.status(201).json(notebook);
})
);

Key practices in this structure:

  • Permissions are explicit and enforced at the top of each route
  • Input is validated immediately upon receipt
  • Errors are handled consistently, with structured HTTP error responses

HTTP Error Handling

Custom error classes (e.g. BadRequestError, ForbiddenError, NotFoundError) are thrown within routes. These are intercepted by the middleware and converted into:

  • Proper HTTP status codes (400, 403, 404, etc.)
  • Sanitized messages appropriate for client consumption
  • Structured response objects for easier front-end handling

Unhandled exceptions are logged and converted to a generic 500 response with no internal detail leakage.

Benefits of This Structure

  • Strong consistency across routes
  • Centralized enforcement of authentication and permissions
  • Minimal boilerplate for new API development
  • Easier onboarding for developers unfamiliar with the system
  • Tight integration with monitoring, logging, and error tracking systems