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:

  1. req.user.organizationId (authenticated user)
  2. req.query.organizationId
  3. x-tenant-slug header → 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.