Node / Express Guide
Middleware
Tenant resolution, auth, role guards, and app-level middleware.
Tenant Middleware
Resolves organizationId from the request context and attaches it to req.organizationId. It looks in three places in order:
req.user.organizationId(authenticated user)req.query.organizationIdx-tenant-slugheader → DB lookup
// src/middleware/tenant.middleware.ts
import { Request, Response, NextFunction } from 'express'
import { ObjectId } from 'mongodb'
import { useOrganizationRepo } from '../repositories/organization.repo'
import { UnprocessableEntityError } from '@codisolutions23/node-utils'
function resolveTenant(required: boolean) {
return async (req: any, res: Response, next: NextFunction) => {
try {
// 1. From authenticated user
if (req.user?.organizationId) {
req.organizationId = req.user.organizationId
return next()
}
// 2. From query param
if (req.query.organizationId) {
req.organizationId = req.query.organizationId
return next()
}
// 3. From x-tenant-slug header
const slug = req.headers['x-tenant-slug']
if (slug) {
const repo = useOrganizationRepo()
const org = await repo.getBySlug(slug as string)
if (org) {
req.organizationId = org._id!.toString()
return next()
}
}
if (required) {
return next(new UnprocessableEntityError('Organization context is required.'))
}
next()
} catch (err) {
next(err)
}
}
}
export const resolveRequiredTenant = resolveTenant(true)
export const resolveOptionalTenant = resolveTenant(false)
Usage on routes:
// Optional — continues if org cannot be determined
router.post('/login', resolveOptionalTenant, login)
// Required — throws 422 if org cannot be determined
router.get('/', resolveRequiredTenant, getItems)
Auth Middleware
Validates the Authorization: Bearer <token> header and checks the JWT is not revoked:
import { authenticate } from '@codisolutions23/node-utils'
import { useRevokedTokenRepo } from '../repositories/revoked-token.repo'
import { accessTokenSecret } from '../config'
const { existsByJti } = useRevokedTokenRepo()
const authMiddleware = authenticate(accessTokenSecret, existsByJti)
router.get('/profile', authMiddleware, getProfile)
On success, attaches req.user (decoded JWT payload) and req.token (raw token) to the request.
Role Guard Middleware
// src/middleware/role-guard.middleware.ts
import { authenticate } from '@codisolutions23/node-utils'
import { ForbiddenError } from '@codisolutions23/node-utils'
import { UserType } from '../enums/user.enum'
import type { TUserType } from '../types/common.types'
function requireRoles(roles: TUserType[], secret: string, existsByJti: Function) {
const auth = authenticate(secret, existsByJti)
return [
auth,
(req: any, res: any, next: any) => {
if (!req.user || !roles.includes(req.user.type)) {
return next(new ForbiddenError('You do not have permission to perform this action.'))
}
next()
},
]
}
// Pre-composed role guards
export const requireAdmin = (secret: string, ebjti: Function) =>
requireRoles([UserType.ADMIN], secret, ebjti)
export const requireOwner = (secret: string, ebjti: Function) =>
requireRoles([UserType.OWNER, UserType.ADMIN], secret, ebjti)
export const requireBranchManagerOrAbove = (secret: string, ebjti: Function) =>
requireRoles([UserType.ADMIN, UserType.OWNER, UserType.BRANCH_MANAGER], secret, ebjti)
Usage in a route file:
const adminMiddleware = requireAdmin(accessTokenSecret, existsByJti)
const branchManagerMiddleware = requireBranchManagerOrAbove(accessTokenSecret, existsByJti)
router.get('/', authMiddleware, getAll)
router.post('/', branchManagerMiddleware, create)
router.delete('/:id', adminMiddleware, remove)
App-Level Middleware Order
Register middleware in this order in src/app.ts:
app.use(helmet()) // Security headers
app.use(cors(corsOptions)) // CORS
app.use(express.json()) // JSON body parser
app.use(express.urlencoded({ extended: true })) // URL-encoded body parser
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 200 })) // Rate limiting
app.use('/api', routes) // Routes
app.use(errorHandler) // Error handler — must be last
errorHandler must be registered after all routes.