const { AsyncLocalStorage } = require('async_hooks');
const storage = new AsyncLocalStorage();
function runWithTenant(tenantId, userId, callback) {
return storage.run({ tenantId, userId }, callback);
}
function getTenantId() {
const store = storage.getStore();
return store ? store.tenantId : null;
}
function requireTenantId() {
const tenantId = getTenantId();
if (!tenantId) {
throw new Error('No tenant context: request ran outside tenantResolver');
}
return tenantId;
}
function getUserId() {
const store = storage.getStore();
return store ? store.userId : null;
}
module.exports = { runWithTenant, getTenantId, requireTenantId, getUserId };
const jwt = require('jsonwebtoken');
const { runWithTenant } = require('./tenantContext');
function tenantResolver(req, res, next) {
const auth = req.get('authorization') || '';
const token = auth.startsWith('Bearer ') ? auth.slice(7) : null;
if (!token) {
return res.status(401).json({ error: 'missing bearer token' });
}
let claims;
try {
claims = jwt.verify(token, process.env.JWT_SECRET);
} catch (err) {
return res.status(401).json({ error: 'invalid token' });
}
const claimedTenant = claims.tenant_id;
const headerTenant = req.get('x-tenant-id');
if (!claimedTenant) {
return res.status(403).json({ error: 'token has no tenant claim' });
}
// Defeat cross-tenant token replay when a header is present.
if (headerTenant && headerTenant !== claimedTenant) {
return res.status(403).json({ error: 'tenant mismatch' });
}
runWithTenant(claimedTenant, claims.sub, () => {
next();
});
}
module.exports = tenantResolver;
const { requireTenantId } = require('./tenantContext');
class ScopedRepository {
constructor(pool, table) {
this.pool = pool;
this.table = table;
}
async findById(id) {
const tenantId = requireTenantId();
const sql = `SELECT * FROM ${this.table} WHERE tenant_id = $1 AND id = $2`;
const { rows } = await this.pool.query(sql, [tenantId, id]);
return rows[0] || null;
}
async list({ limit = 50, offset = 0 } = {}) {
const tenantId = requireTenantId();
const sql = `SELECT * FROM ${this.table}
WHERE tenant_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3`;
const { rows } = await this.pool.query(sql, [tenantId, limit, offset]);
return rows;
}
async insert(attrs) {
const tenantId = requireTenantId();
const cols = Object.keys(attrs);
const values = Object.values(attrs);
const placeholders = cols.map((_, i) => `$${i + 2}`).join(', ');
const sql = `INSERT INTO ${this.table} (tenant_id, ${cols.join(', ')})
VALUES ($1, ${placeholders})
RETURNING *`;
const { rows } = await this.pool.query(sql, [tenantId, ...values]);
return rows[0];
}
async delete(id) {
const tenantId = requireTenantId();
const sql = `DELETE FROM ${this.table} WHERE tenant_id = $1 AND id = $2`;
const { rowCount } = await this.pool.query(sql, [tenantId, id]);
return rowCount > 0;
}
}
module.exports = ScopedRepository;
const express = require('express');
const tenantResolver = require('./tenantResolver');
const ScopedRepository = require('./ScopedRepository');
const pool = require('./db');
const router = express.Router();
const orders = new ScopedRepository(pool, 'orders');
router.use(tenantResolver);
router.get('/', async (req, res, next) => {
try {
const limit = Math.min(Number(req.query.limit) || 50, 200);
const rows = await orders.list({ limit, offset: Number(req.query.offset) || 0 });
res.json({ data: rows });
} catch (err) {
next(err);
}
});
router.get('/:id', async (req, res, next) => {
try {
const order = await orders.findById(req.params.id);
if (!order) return res.status(404).json({ error: 'not found' });
res.json(order);
} catch (err) {
next(err);
}
});
router.post('/', async (req, res, next) => {
try {
const created = await orders.insert({
customer_id: req.body.customer_id,
amount_cents: req.body.amount_cents,
status: 'pending',
});
res.status(201).json(created);
} catch (err) {
next(err);
}
});
module.exports = router;
Multi-tenant SaaS backends must guarantee that a request for tenant A can never read or write tenant B's rows. Passing a tenantId argument through every function is fragile: one forgotten parameter becomes a data leak. This snippet enforces tenancy at two layers — a resolver middleware that establishes an ambient request context, and a repository that refuses to run any query without it.
In tenantContext.js, Node's AsyncLocalStorage holds a per-request store that survives across await boundaries and nested callbacks. runWithTenant opens a store for the lifetime of the request, and getTenantId reads it back later without threading it through call signatures. requireTenantId throws when the context is missing, turning a silent bug into a loud failure at the exact point a query would have leaked.
tenantResolver.js is the Express middleware that populates that context. It extracts the tenant from a signed JWT claim and cross-checks it against the X-Tenant-Id header, rejecting mismatches with a 403 to defeat token replay across tenants. Crucially it calls next() inside runWithTenant, so every downstream handler, service, and repository executes within the store. A subtle pitfall handled here: the callback form of runWithTenant must wrap next rather than returning before the async chain settles, otherwise the context would unwind too early.
ScopedRepository.js is where the guarantee is cashed in. Every method calls requireTenantId() and injects a tenant_id = $1 predicate into the SQL, so callers physically cannot query outside their tenant. insert stamps the tenant id onto new rows, and update/delete scope their WHERE clauses the same way. Because the id comes from the ambient context and not from method arguments, application code stays clean while isolation is centralized and auditable.
orders.routes.js shows the payoff: handlers read req and body data but never mention tenantId — the repository resolves it implicitly. The trade-off is that the pattern relies on the middleware always running first; mounting the repository behind an unauthenticated route would throw rather than leak, which is the safe failure mode. For defense in depth this pairs well with Postgres row-level security, but the application-layer scope alone eliminates the most common class of cross-tenant bugs.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.