This guide outlines the standards that should be followed to ensure consistency across all route files in the API.
Each route file should follow this structure:
// 1. Imports
const express = require('express');
const someController = require('../controllers/someController');
const authenticate = require('../middleware/authenticate');
const authorize = require('../middleware/authorize');
const validateContext = require('../middleware/contextValidator');
const { validate, schemas } = require('../utils/validation');
// 2. Router Initialization
const router = express.Router();
// 3. Global Middleware (if applicable)
router.use(authenticate); // For protected routes only
// 4. Route Definitions
/**
* @route HTTP_METHOD /path
* @desc Description of what the endpoint does
* @access Public|Private (with permission requirements)
*/
router.method('/path',
// Middleware chain
validate(schemas.someSchema, 'params'),
validateContext(),
authorize('permission:action'),
controllerFunction
);
// 5. Export
module.exports = router;
Every route must include JSDoc-style comments with:
/**
* @route HTTP_METHOD /path/with/:params
* @desc Brief description of endpoint functionality
* @access Public|Private (requires permission:action)
*/
The middleware chain for protected routes should follow this order:
- Parameter Validation:
validate(schemas.paramSchema, 'params')
- Context Validation:
validateContext()
(for org/env scoped routes) - Authorization:
authorize('permission:action')
- Request Body Validation:
validate(schemas.bodySchema)
(for POST/PUT/PATCH) - Controller Function:
controllerFunction
Example:
router.post('/:orgId/members/invite',
validate(schemas.orgIdParam, 'params'),
validateContext(),
authorize('memberships:manage'),
validate(schemas.organizationInviteRequest),
organizationController.inviteUserToOrganization
);
- No global authentication middleware
- Individual routes that need auth use
authenticate
middleware
// No router.use(authenticate)
router.post('/login', validate(schemas.loginRequest), authController.requestMagicLink);
router.post('/logout', authenticate, authController.logout);
- Apply authentication globally to all routes
- Individual routes use authorization middleware
// Apply to all routes in this file
router.use(authenticate);
router.get('/organizations/:orgId',
validate(schemas.orgIdParam, 'params'),
validateContext(),
authorize('organizations:read'),
organizationController.getOrganization
);
Always validate route parameters:
validate(schemas.orgIdParam, 'params') // Single param
validate(schemas.orgAndEnvParams, 'params') // Multiple params
validate(schemas.publicationCredentialParams, 'params') // Complex params
Validate request bodies for data modification endpoints:
validate(schemas.organizationInviteRequest) // POST requests
validate(schemas.userUpdateRequest) // PATCH requests
validate(schemas.publicationCredentialCreateRequest) // Creation
Use descriptive permission names that follow the pattern resource:action
:
- Read permissions:
organizations:read
,credentials:read
,memberships:read
- List permissions:
environments:list
- Management permissions:
memberships:manage
,credentials:manage
,environments:manage
For organization/environment scoped routes, always include validateContext()
:
router.get('/organizations/:orgId/environments/:envId/some-resource',
validate(schemas.orgAndEnvParams, 'params'),
validateContext(), // Ensures user has access to org/env (compare JWT to path)
authorize('resource:read'),
controller.getResource
);
Group related routes logically within the file:
- Core resource operations (CRUD on main resource)
- Sub-resource operations (nested resources)
- Administrative operations (special actions)
Follow RESTful conventions:
- GET
/resource
- List resources - GET
/resource/:id
- Get single resource - POST
/resource
- Create resource - PUT/PATCH
/resource/:id
- Update resource - DELETE
/resource/:id
- Delete resource
For nested resources:
- GET
/organizations/:orgId/environments/:envId/publications/:pubId/credentials
- POST
/organizations/:orgId/environments/:envId/publications/:pubId/credentials
router.post('/organizations/:orgId/environments/:envId/members/:membershipId/impersonate',
validate(schemas.orgEnvMemberParams, 'params'),
validateContext(),
authorize('memberships:manage'),
userController.impersonateMember
);
router.get('/organizations/:orgId/members/:membershipId/permissions',
validate(schemas.orgIdAndMemberParams, 'params'),
validateContext(),
authorize('memberships:manage'),
userController.getMemberPermissions
);
Error handling is managed by:
- Validation middleware - Catches validation errors
- Authorization middleware - Catches permission errors
- Controller asyncHandler - Catches business logic errors
- Global error middleware - Catches all unhandled errors
Routes should not include try/catch blocks as controllers handle this with asyncHandler
.
Route files should be named as {resource}Routes.js
:
authRoutes.js
- Authentication endpointsorganizationRoutes.js
- Organization-scoped endpointsuserRoutes.js
- User-specific endpointsadminRoutes.js
- Administrative endpoints