Jul 2, 2026
Write code the next engineer can actually change
The hardest code to work with is not clever code. It is code where you cannot tell what it is supposed to do, what it depends on, or what will break if you change one line.
I have shipped plenty of that early in my career. The feature worked. Tests passed, if there were tests. Then someone, often me three months later, needed to add a field or handle a new edge case, and a two-hour task became two days because the handler did six unrelated things in one function, threw errors from three layers deep, and hid its assumptions in implicit global state.
This post is a full walkthrough of how I write backend code now: one real endpoint refactored step by step, a second smaller example, how I test it, and a checklist I use before merging. None of this requires a framework rewrite. It is mostly discipline at the function boundary.
The problem: one handler doing everything
Volunteer management SaaS: assigning a volunteer to an open shift. Simple on paper. The first version looked like this:
app.post("/shifts/:id/assign", async (req, res) => {
const shift = await db.query.shifts.findFirst({
where: eq(shifts.id, req.params.id),
});
if (!shift) return res.status(404).json({ error: "not found" });
if (shift.status !== "open") return res.status(400).json({ error: "closed" });
const volunteer = await db.query.volunteers.findFirst({
where: eq(volunteers.id, req.body.volunteerId),
});
if (!volunteer) return res.status(404).json({ error: "volunteer not found" });
const existing = await db.query.assignments.findFirst({
where: and(
eq(assignments.volunteerId, volunteer.id),
eq(assignments.shiftId, shift.id),
),
});
if (existing) return res.status(409).json({ error: "already assigned" });
const overlap = await db.query.assignments.findMany({
where: and(
eq(assignments.volunteerId, volunteer.id),
between(assignments.startsAt, shift.startsAt, shift.endsAt),
),
});
if (overlap.length) return res.status(409).json({ error: "schedule conflict" });
await db.insert(assignments).values({
shiftId: shift.id,
volunteerId: volunteer.id,
assignedBy: req.user.id,
});
await notifyVolunteer(volunteer, shift);
res.json({ ok: true });
});
What is wrong here is not that it is long. It is that every layer is mixed:
| Concern | Where it lives now |
|---|---|
| HTTP status codes | Inside business logic path |
| Input shape | req.body passed raw |
| Authorization | Assumes req.user exists |
| Business rules | Inline with DB calls |
| Persistence | Inline |
| Side effects (email) | After insert, no rollback story |
To test “schedule conflict” you need an HTTP server, a mock request, a database, and hope the ORM does what you think. To change how conflicts work you edit the same function that maps HTTP codes. To add audit logging you paste more lines into an already dense block.
Step 1: name the job and return data, not exceptions
The core question: what is the single job of assignment?
Answer: given a shift, a volunteer, and who is performing the action, either create an assignment or explain why not, without knowing anything about HTTP.
type AssignFailure =
| "shift_not_found"
| "shift_closed"
| "volunteer_not_found"
| "already_assigned"
| "schedule_conflict"
| "volunteer_inactive";
type AssignResult =
| { ok: true; assignmentId: string }
| { ok: false; reason: AssignFailure };
async function assignVolunteerToShift(input: {
shiftId: string;
volunteerId: string;
assignedBy: string;
organizationId: string;
}): Promise<AssignResult> {
const shift = await shiftsRepo.findById(input.organizationId, input.shiftId);
if (!shift) return { ok: false, reason: "shift_not_found" };
if (shift.status !== "open") return { ok: false, reason: "shift_closed" };
const volunteer = await volunteersRepo.findById(input.organizationId, input.volunteerId);
if (!volunteer) return { ok: false, reason: "volunteer_not_found" };
if (!volunteer.isActive) return { ok: false, reason: "volunteer_inactive" };
if (await assignmentsRepo.exists(input.organizationId, input.shiftId, input.volunteerId)) {
return { ok: false, reason: "already_assigned" };
}
if (
await assignmentsRepo.hasOverlap(
input.organizationId,
input.volunteerId,
shift.startsAt,
shift.endsAt,
)
) {
return { ok: false, reason: "schedule_conflict" };
}
const assignmentId = await assignmentsRepo.create({
organizationId: input.organizationId,
shiftId: shift.id,
volunteerId: volunteer.id,
assignedBy: input.assignedBy,
});
return { ok: true, assignmentId };
}
Notice organizationId on every repo call. Multi-tenant scoping belongs in the data layer, not as a hope that every handler remembers the filter.
Why results instead of throw?
Thrown exceptions are fine for truly exceptional cases: disk full, network partition. “Volunteer already assigned” is not exceptional. It is an expected business outcome. Returning it as data means:
- Callers choose how to surface it (HTTP 409, inline form error, silent skip in a batch job)
- Tests assert on values, not
try/catch - Logs can record
reasonwithout stack traces for normal paths
Step 2: thin HTTP layer as a translator
HTTP is one adapter. Webhooks, CLI tools, and background jobs may call the same function.
const ASSIGN_STATUS: Record<AssignFailure, number> = {
shift_not_found: 404,
volunteer_not_found: 404,
shift_closed: 400,
volunteer_inactive: 400,
already_assigned: 409,
schedule_conflict: 409,
};
const AssignBody = z.object({
volunteerId: z.string().uuid(),
});
app.post("/orgs/:orgId/shifts/:shiftId/assign", async (req, res) => {
const parsed = AssignBody.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: "invalid_body", details: parsed.error.flatten() });
}
if (req.user.organizationId !== req.params.orgId) {
return res.status(403).json({ error: "forbidden" });
}
const result = await assignVolunteerToShift({
organizationId: req.params.orgId,
shiftId: req.params.shiftId,
volunteerId: parsed.data.volunteerId,
assignedBy: req.user.id,
});
if (!result.ok) {
return res.status(ASSIGN_STATUS[result.reason]).json({ error: result.reason });
}
// Side effects after success, see step 4
await enqueueAssignmentNotification(result.assignmentId);
return res.status(201).json({ assignmentId: result.assignmentId });
});
Validation happens once at the door with Zod. Authorization is explicit. Status mapping is a table, not scattered if branches.
Step 3: repositories own SQL shape
Handlers should not know table names. Repositories encapsulate queries and always scope by tenant:
function assignmentsRepo(db: Db) {
return {
async exists(orgId: string, shiftId: string, volunteerId: string) {
const row = await db.query.assignments.findFirst({
where: and(
eq(assignments.organizationId, orgId),
eq(assignments.shiftId, shiftId),
eq(assignments.volunteerId, volunteerId),
),
columns: { id: true },
});
return Boolean(row);
},
async hasOverlap(orgId: string, volunteerId: string, start: Date, end: Date) {
const rows = await db
.select({ id: assignments.id })
.from(assignments)
.where(
and(
eq(assignments.organizationId, orgId),
eq(assignments.volunteerId, volunteerId),
lt(assignments.startsAt, end),
gt(assignments.endsAt, start),
),
)
.limit(1);
return rows.length > 0;
},
async create(data: {
organizationId: string;
shiftId: string;
volunteerId: string;
assignedBy: string;
}) {
const [row] = await db.insert(assignments).values(data).returning({ id: assignments.id });
return row.id;
},
};
}
When the slow query post becomes relevant, you optimize in one place. When schema changes, you update one module.
Step 4: side effects are not part of the transaction (usually)
Email and push notifications should not run inside the same logical unit as the insert unless you enjoy duplicate emails on retry.
Pattern I use:
- Commit the assignment in the database
- Enqueue a job:
{ type: "assignment.created", assignmentId } - Worker sends notification idempotently
async function enqueueAssignmentNotification(assignmentId: string) {
await queue.publish("notifications", {
idempotencyKey: `assignment-notify:${assignmentId}`,
assignmentId,
});
}
async function handleAssignmentNotification(event: { assignmentId: string }) {
const sent = await dedupeStore.markIfNew(event.assignmentId);
if (!sent) return;
const ctx = await assignmentsRepo.loadNotificationContext(event.assignmentId);
if (!ctx) return;
await email.send({
to: ctx.volunteerEmail,
template: "shift-assigned",
data: { shiftName: ctx.shiftName, startsAt: ctx.startsAt },
});
}
If the HTTP handler crashes after insert but before enqueue, a reconciliation job catches orphaned assignments. If enqueue runs twice, dedupe prevents double email.
Step 5: make illegal states hard to represent
String status fields drift. status = "paid" with no paidAt. status = "cancelled" with inventory still held.
For order flows I model lifecycle with discriminated unions:
type DraftOrder = {
kind: "draft";
id: string;
items: LineItem[];
};
type PlacedOrder = {
kind: "placed";
id: string;
items: LineItem[];
placedAt: Date;
};
type PaidOrder = {
kind: "paid";
id: string;
items: LineItem[];
placedAt: Date;
paidAt: Date;
paymentId: string;
};
type Order = DraftOrder | PlacedOrder | PaidOrder;
function markPaid(order: PlacedOrder, paymentId: string): PaidOrder {
return {
...order,
kind: "paid",
paidAt: new Date(),
paymentId,
};
}
markPaid(draftOrder, "pay_123") is a compile error. The type system documents allowed transitions better than a wiki page nobody reads.
For simpler domains, a small state machine function beats a boolean zoo:
type ShiftStatus = "draft" | "open" | "filled" | "cancelled";
const ALLOWED: Record<ShiftStatus, ShiftStatus[]> = {
draft: ["open", "cancelled"],
open: ["filled", "cancelled"],
filled: ["cancelled"],
cancelled: [],
};
function transitionShift(current: ShiftStatus, next: ShiftStatus): ShiftStatus {
if (!ALLOWED[current].includes(next)) {
throw new Error(`invalid transition ${current} -> ${next}`);
}
return next;
}
Step 6: tests that actually protect you
With the refactor, unit tests need no HTTP:
describe("assignVolunteerToShift", () => {
it("rejects when volunteer has overlapping shift", async () => {
const orgId = "org_1";
await seedShift({ id: "shift_a", startsAt: hour(9), endsAt: hour(12) });
await seedShift({ id: "shift_b", startsAt: hour(10), endsAt: hour(13) });
await seedVolunteer({ id: "vol_1" });
await assignVolunteerToShift({
organizationId: orgId,
shiftId: "shift_a",
volunteerId: "vol_1",
assignedBy: "admin_1",
});
const result = await assignVolunteerToShift({
organizationId: orgId,
shiftId: "shift_b",
volunteerId: "vol_1",
assignedBy: "admin_1",
});
expect(result).toEqual({ ok: false, reason: "schedule_conflict" });
});
it("creates assignment when all rules pass", async () => {
await seedShift({ id: "shift_1", status: "open" });
await seedVolunteer({ id: "vol_1", isActive: true });
const result = await assignVolunteerToShift({
organizationId: "org_1",
shiftId: "shift_1",
volunteerId: "vol_1",
assignedBy: "admin_1",
});
expect(result.ok).toBe(true);
if (result.ok) {
const row = await db.query.assignments.findFirst({
where: eq(assignments.id, result.assignmentId),
});
expect(row).toBeTruthy();
}
});
});
Integration tests still matter for HTTP wiring and auth. Unit tests catch rule changes fast.
Second example: payment webhook handler
Webhooks are where messy code becomes expensive: duplicate charges, missed events, unverifiable signatures.
Bad version:
app.post("/webhooks/stripe", async (req, res) => {
const event = req.body;
if (event.type === "payment_intent.succeeded") {
await db.update(orders).set({ status: "paid" }).where(eq(orders.id, event.data.orderId));
await sendReceipt(event.data.orderId);
}
res.send("ok");
});
Problems: no signature verification, no idempotency, raw body trust, side effects inline.
Better shape:
app.post("/webhooks/stripe", async (req, res) => {
const signature = req.headers["stripe-signature"];
if (!signature) return res.status(400).send("missing signature");
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, signature, webhookSecret);
} catch {
return res.status(400).send("invalid signature");
}
const handled = await webhookStore.markProcessed(event.id);
if (!handled) return res.status(200).send("already processed");
await webhookDispatcher.dispatch(event);
return res.status(200).send("ok");
});
Dispatcher routes to typed handlers. payment_intent.succeeded calls the same markOrderPaid service the admin UI would use, so there is one path for state changes.
Habits that compound
Read before you write. Twenty minutes reading call sites, migrations, and related cron jobs saves days of incident response.
Log decisions, not noise.
logger.info("assignment_attempt", {
organizationId: input.organizationId,
shiftId: input.shiftId,
volunteerId: input.volunteerId,
shiftStatus: shift?.status,
overlapFound: overlap,
});
Prefer boring names. findOpenShiftById beats getShift. hasScheduleConflict beats checkOverlap.
Keep functions small enough to hold in your head. If you need scroll to see the end, it is probably doing two jobs.
Leave a short comment only when the why is non-obvious, not narrating what the code does.
Checklist before I merge handler code
- Is business logic free of
req/res? - Are expected failures returned as data?
- Is input validated once at the boundary?
- Is tenant scope enforced in repos, not hoped for in handlers?
- Are side effects async/idempotent after commit?
- Is there a unit test for each business rule?
- Can the next engineer change one rule without touching HTTP mapping?
What I still get wrong
Deadlines still produce fat handlers. I still reach for a boolean flag when a state machine would be clearer. The goal is not perfection. It is leaving the codebase easier to change than I found it.
Code that survives is not code that never breaks. It is code where, when something breaks, someone can read it, reason about it, and fix it without fear. That is worth the extra twenty minutes on every PR.