Resume

Backend API example

Approval workflow REST API walkthrough

A public-safe walkthrough designed to show how I handle workflow rules, request and response contracts, role-aware actions, code structure, and testable behavior.

Resume context Scenario walkthrough Code and tests

Skills demonstrated

What this walkthrough is meant to show

  • REST API contract design with explicit workflow endpoints
  • Business-rule handling beyond simple payload validation
  • Role-aware authorization and separation of duties
  • Audit trail modeling and state transitions
  • Code and test structure that a reviewer can inspect quickly

Inspection prompts

What to look for before you read every line

  • How validation, authorization, and business-rule failures are separated
  • How state transitions stay explicit instead of hiding inside controller branches
  • How response shapes remain stable across normal and failure scenarios
  • How the selected code and tests line up with the behavior being demonstrated

Problem and contract

The domain is small on purpose so the workflow logic stays visible.

Approval APIs often look simple on the surface, but the useful backend signal is in the rules: who can act, when a request changes state, how validation differs from authorization, and what evidence gets recorded for later review.

This example keeps the domain business-neutral, but it uses the same kinds of constraints I care about in production APIs: narrow contracts, visible workflow state, structured error responses, and testable decisions.

POST /approval-requests

Create a request and derive its first workflow state.

POST /approval-requests/:id/approve

Move a pending request to approved when role and workflow rules permit it.

POST /approval-requests/:id/reject

Reject a pending request with an audit note.

Guided scenarios

Inspect the workflow one scenario at a time.

Each scenario maps the business behavior to request and response evidence, implementation code, and test coverage.

A standard request moves from submission to approval.

This is the normal reviewer path: create the request, validate it, approve it, and record the audit trail.

What this demonstrates

The API supports a normal business workflow with stable response shapes, explicit state transitions, and visible audit history.

Design tradeoff

I kept the contract narrow and stateful. The API surface is small, but the workflow rules stay explicit instead of hiding inside controller code.

Create approval request

POST /approval-requests

Request

{
  "title": "Renew analytics workspace seats",
  "amount": 1200,
  "currency": "CAD",
  "costCenter": "CC-2040",
  "justification": "Keeps the delivery team on the supported analytics environment.",
  "requesterId": "u-requester"
}

Response

{
  "ok": true,
  "status": 201,
  "body": {
    "request": {
      "requestId": "APR-1001",
      "title": "Renew analytics workspace seats",
      "amount": 1200,
      "currency": "CAD",
      "costCenter": "CC-2040",
      "justification": "Keeps the delivery team on the supported analytics environment.",
      "requesterId": "u-requester",
      "requiredApprovalRole": "manager",
      "state": "pending",
      "approvedBy": null,
      "auditTrail": [
        {
          "type": "request_created",
          "actorId": "u-requester",
          "at": "2026-05-15T09:00:00Z",
          "note": "Request submitted for review"
        }
      ]
    }
  }
}

Approve request

POST /approval-requests/APR-1001/approve

Request

{
  "actorId": "u-manager",
  "role": "manager"
}

Response

{
  "ok": true,
  "status": 200,
  "body": {
    "request": {
      "requestId": "APR-1001",
      "title": "Renew analytics workspace seats",
      "amount": 1200,
      "currency": "CAD",
      "costCenter": "CC-2040",
      "justification": "Keeps the delivery team on the supported analytics environment.",
      "requesterId": "u-requester",
      "requiredApprovalRole": "manager",
      "state": "approved",
      "approvedBy": "u-manager",
      "auditTrail": [
        {
          "type": "request_created",
          "actorId": "u-requester",
          "at": "2026-05-15T09:00:00Z",
          "note": "Request submitted for review"
        },
        {
          "type": "request_approved",
          "actorId": "u-manager",
          "at": "2026-05-15T09:05:00Z",
          "note": "Approved by manager"
        }
      ]
    }
  }
}

Code path

Implementation excerpt

export const approveRequest = (
  request: ApprovalRequest,
  actor: Actor,
  options?: { now?: string }
): ApiResult<{ request: ApprovalRequest }> => {
  if (actor.role !== "manager" && actor.role !== "finance") {
    return {
      ok: false,
      status: 403,
      body: {
        error: {
          code: "approval_role_required",
          message: "Only manager or finance reviewers can approve requests."
        }
      }
    };
  }

  if (request.state !== "pending") {
    return {
      ok: false,
      status: 409,
      body: {
        error: {
          code: "request_not_pending",
          message: "Only pending requests can move to approval."
        }
      }
    };
  }

  if (actor.userId === request.requesterId) {
    return {
      ok: false,
      status: 409,
      body: {
        error: {
          code: "separation_of_duties_required",
          message: "The requester cannot approve their own request."
        }
      }
    };
  }

  if (request.requiredApprovalRole === "finance" && actor.role !== "finance") {
    return {
      ok: false,
      status: 422,
      body: {
        error: {
          code: "finance_review_required",
          message: "Requests above the manager approval limit must be approved by finance.",
          nextAction: "Route the request to a finance reviewer."
        }
      }
    };
  }

  return {
    ok: true,
    status: 200,
    body: {
      request: appendAuditEvent(
        {
          ...request,
          state: "approved",
          approvedBy: actor.userId
        },
        {
          type: "request_approved",
          actorId: actor.userId,
          at: options?.now,
          note: `Approved by ${actor.role}`
        }
      )
    }
  };
};
Show the full workflow module
export type ApprovalState = "pending" | "approved" | "rejected";
export type ApprovalRole = "requester" | "manager" | "finance";

export interface ApprovalRequestInput {
  title: string;
  amount: number;
  currency: "CAD" | "USD";
  costCenter: string;
  justification: string;
  requesterId: string;
}

export interface Actor {
  userId: string;
  role: ApprovalRole;
}

export interface AuditEvent {
  type: "request_created" | "request_approved" | "request_rejected";
  actorId: string;
  at: string;
  note: string;
}

export interface ApprovalRequest {
  requestId: string;
  title: string;
  amount: number;
  currency: "CAD" | "USD";
  costCenter: string;
  justification: string;
  requesterId: string;
  requiredApprovalRole: "manager" | "finance";
  state: ApprovalState;
  approvedBy: string | null;
  auditTrail: AuditEvent[];
}

export interface ApiErrorBody {
  error: {
    code: string;
    message: string;
    details?: string[];
    nextAction?: string;
  };
}

export type ApiResult<T> =
  | {
      ok: true;
      status: number;
      body: T;
    }
  | {
      ok: false;
      status: number;
      body: ApiErrorBody;
    };

const MANAGER_LIMIT = 2500;

const validationErrors = (input: ApprovalRequestInput) => {
  const errors: string[] = [];

  if (input.title.trim().length < 8) {
    errors.push("title must be at least 8 characters");
  }

  if (input.amount <= 0) {
    errors.push("amount must be greater than zero");
  }

  if (input.justification.trim().length < 20) {
    errors.push("justification must be at least 20 characters");
  }

  if (!/^CC-[0-9]{4}$/u.test(input.costCenter)) {
    errors.push("costCenter must use the CC-0000 format");
  }

  return errors;
};

const appendAuditEvent = (
  request: ApprovalRequest,
  event: Omit<AuditEvent, "at"> & { at?: string }
): ApprovalRequest => ({
  ...request,
  auditTrail: [
    ...request.auditTrail,
    {
      ...event,
      at: event.at ?? new Date().toISOString()
    }
  ]
});

// snippet:create-request-start
export const createApprovalRequest = (
  input: ApprovalRequestInput,
  options?: { requestId?: string; now?: string }
): ApiResult<{ request: ApprovalRequest }> => {
  const errors = validationErrors(input);

  if (errors.length > 0) {
    return {
      ok: false,
      status: 422,
      body: {
        error: {
          code: "validation_failed",
          message: "The request payload did not pass workflow validation.",
          details: errors
        }
      }
    };
  }

  const request: ApprovalRequest = {
    requestId: options?.requestId ?? "APR-1001",
    title: input.title,
    amount: input.amount,
    currency: input.currency,
    costCenter: input.costCenter,
    justification: input.justification,
    requesterId: input.requesterId,
    requiredApprovalRole: input.amount > MANAGER_LIMIT ? "finance" : "manager",
    state: "pending",
    approvedBy: null,
    auditTrail: []
  };

  return {
    ok: true,
    status: 201,
    body: {
      request: appendAuditEvent(request, {
        type: "request_created",
        actorId: input.requesterId,
        at: options?.now,
        note: "Request submitted for review"
      })
    }
  };
};
// snippet:create-request-end

// snippet:approve-request-start
export const approveRequest = (
  request: ApprovalRequest,
  actor: Actor,
  options?: { now?: string }
): ApiResult<{ request: ApprovalRequest }> => {
  if (actor.role !== "manager" && actor.role !== "finance") {
    return {
      ok: false,
      status: 403,
      body: {
        error: {
          code: "approval_role_required",
          message: "Only manager or finance reviewers can approve requests."
        }
      }
    };
  }

  if (request.state !== "pending") {
    return {
      ok: false,
      status: 409,
      body: {
        error: {
          code: "request_not_pending",
          message: "Only pending requests can move to approval."
        }
      }
    };
  }

  if (actor.userId === request.requesterId) {
    return {
      ok: false,
      status: 409,
      body: {
        error: {
          code: "separation_of_duties_required",
          message: "The requester cannot approve their own request."
        }
      }
    };
  }

  if (request.requiredApprovalRole === "finance" && actor.role !== "finance") {
    return {
      ok: false,
      status: 422,
      body: {
        error: {
          code: "finance_review_required",
          message: "Requests above the manager approval limit must be approved by finance.",
          nextAction: "Route the request to a finance reviewer."
        }
      }
    };
  }

  return {
    ok: true,
    status: 200,
    body: {
      request: appendAuditEvent(
        {
          ...request,
          state: "approved",
          approvedBy: actor.userId
        },
        {
          type: "request_approved",
          actorId: actor.userId,
          at: options?.now,
          note: `Approved by ${actor.role}`
        }
      )
    }
  };
};
// snippet:approve-request-end

export const rejectRequest = (
  request: ApprovalRequest,
  actor: Actor,
  reason: string,
  options?: { now?: string }
): ApiResult<{ request: ApprovalRequest }> => {
  if (request.state !== "pending") {
    return {
      ok: false,
      status: 409,
      body: {
        error: {
          code: "request_not_pending",
          message: "Only pending requests can be rejected."
        }
      }
    };
  }

  if (actor.role !== "manager" && actor.role !== "finance") {
    return {
      ok: false,
      status: 403,
      body: {
        error: {
          code: "approval_role_required",
          message: "Only manager or finance reviewers can reject requests."
        }
      }
    };
  }

  return {
    ok: true,
    status: 200,
    body: {
      request: appendAuditEvent(
        {
          ...request,
          state: "rejected"
        },
        {
          type: "request_rejected",
          actorId: actor.userId,
          at: options?.now,
          note: reason
        }
      )
    }
  };
};

Test coverage

Test mapped to this scenario

it("approves a standard request with manager review", () => {
  const created = createApprovalRequest(
    {
      title: "Renew analytics workspace seats",
      amount: 1200,
      currency: "CAD",
      costCenter: "CC-2040",
      justification: "Keeps the delivery team on the supported analytics environment.",
      requesterId: "u-requester"
    },
    { requestId: "APR-1001", now: "2026-05-15T09:00:00Z" }
  );

  expect(created.ok).toBe(true);
  if (!created.ok) {
    throw new Error("expected create request to succeed");
  }

  const approved = approveRequest(created.body.request, { userId: "u-manager", role: "manager" }, {
    now: "2026-05-15T09:05:00Z"
  });

  expect(approved.ok).toBe(true);
  if (!approved.ok) {
    throw new Error("expected approval to succeed");
  }

  expect(approved.body.request.state).toBe("approved");
  expect(approved.body.request.approvedBy).toBe("u-manager");
  expect(approved.body.request.auditTrail).toHaveLength(2);
});
Show the full test file
import { describe, expect, it } from "vitest";
import { approveRequest, createApprovalRequest, rejectRequest } from "./engine";

// snippet:test-happy-path-start
it("approves a standard request with manager review", () => {
  const created = createApprovalRequest(
    {
      title: "Renew analytics workspace seats",
      amount: 1200,
      currency: "CAD",
      costCenter: "CC-2040",
      justification: "Keeps the delivery team on the supported analytics environment.",
      requesterId: "u-requester"
    },
    { requestId: "APR-1001", now: "2026-05-15T09:00:00Z" }
  );

  expect(created.ok).toBe(true);
  if (!created.ok) {
    throw new Error("expected create request to succeed");
  }

  const approved = approveRequest(created.body.request, { userId: "u-manager", role: "manager" }, {
    now: "2026-05-15T09:05:00Z"
  });

  expect(approved.ok).toBe(true);
  if (!approved.ok) {
    throw new Error("expected approval to succeed");
  }

  expect(approved.body.request.state).toBe("approved");
  expect(approved.body.request.approvedBy).toBe("u-manager");
  expect(approved.body.request.auditTrail).toHaveLength(2);
});
// snippet:test-happy-path-end

// snippet:test-finance-rule-start
it("requires finance review for requests above the manager limit", () => {
  const created = createApprovalRequest(
    {
      title: "Approve year-end disaster recovery rehearsal",
      amount: 8600,
      currency: "CAD",
      costCenter: "CC-2040",
      justification: "Covers the annual rehearsal with contracted vendor and overtime support.",
      requesterId: "u-requester"
    },
    { requestId: "APR-1002", now: "2026-05-15T11:00:00Z" }
  );

  if (!created.ok) {
    throw new Error("expected create request to succeed");
  }

  const result = approveRequest(created.body.request, { userId: "u-manager", role: "manager" }, {
    now: "2026-05-15T11:05:00Z"
  });

  expect(result.ok).toBe(false);
  if (result.ok) {
    throw new Error("expected finance rule to reject manager approval");
  }

  expect(result.status).toBe(422);
  expect(result.body.error.code).toBe("finance_review_required");
});
// snippet:test-finance-rule-end

// snippet:test-unauthorized-start
it("blocks actors without approval permissions", () => {
  const created = createApprovalRequest(
    {
      title: "Renew staging monitoring coverage",
      amount: 640,
      currency: "CAD",
      costCenter: "CC-2040",
      justification: "Maintains non-production monitoring before the next release cycle.",
      requesterId: "u-requester"
    },
    { requestId: "APR-1003", now: "2026-05-15T13:00:00Z" }
  );

  if (!created.ok) {
    throw new Error("expected create request to succeed");
  }

  const result = approveRequest(created.body.request, { userId: "u-contractor", role: "requester" }, {
    now: "2026-05-15T13:04:00Z"
  });

  expect(result.ok).toBe(false);
  if (result.ok) {
    throw new Error("expected approval permissions check to fail");
  }

  expect(result.status).toBe(403);
  expect(result.body.error.code).toBe("approval_role_required");
});
// snippet:test-unauthorized-end

describe("approval workflow extra checks", () => {
  it("rejects invalid create payloads", () => {
    const created = createApprovalRequest(
      {
        title: "Short",
        amount: 0,
        currency: "CAD",
        costCenter: "2040",
        justification: "Too short",
        requesterId: "u-requester"
      },
      { requestId: "APR-2001", now: "2026-05-15T14:00:00Z" }
    );

    expect(created.ok).toBe(false);
    if (created.ok) {
      throw new Error("expected validation to fail");
    }

    expect(created.status).toBe(422);
    expect(created.body.error.details).toContain("amount must be greater than zero");
  });

  it("records rejection decisions with an audit note", () => {
    const created = createApprovalRequest(
      {
        title: "Backfill release support coverage",
        amount: 900,
        currency: "CAD",
        costCenter: "CC-2040",
        justification: "Covers release support while a teammate is on approved leave.",
        requesterId: "u-requester"
      },
      { requestId: "APR-2002", now: "2026-05-15T15:00:00Z" }
    );

    if (!created.ok) {
      throw new Error("expected create request to succeed");
    }

    const rejected = rejectRequest(
      created.body.request,
      { userId: "u-manager", role: "manager" },
      "Budget freeze for the current sprint window.",
      { now: "2026-05-15T15:10:00Z" }
    );

    expect(rejected.ok).toBe(true);
    if (!rejected.ok) {
      throw new Error("expected rejection to succeed");
    }

    expect(rejected.body.request.state).toBe("rejected");
    expect(rejected.body.request.auditTrail.at(-1)?.note).toContain("Budget freeze");
  });
});

Additional checks

The guided scenarios are not the whole test story.

The visible scenarios are the first path I want a reviewer to inspect. The backing test file also covers invalid payload handling and rejection audit notes, so the workflow behavior is not limited to the three guided cases above.

This is the balance I want for the portfolio: enough of the important behavior is front and center, and the deeper detail is one click away for engineers who want to inspect it.