Abdul Ahad QureshiFullstack Engineer
← Index Case 02 / 05 · 2026 · Built from scratch

Fractional.

Client

Fractional Coordinator, a civil rights compliance firm in Portland, Oregon

Role

Built from scratch
Architecture, API, every front end

Stack

Turborepo, NestJS 11, Postgres with Drizzle, Next.js 16, Sanity, Stripe, Cloudflare R2

Live

fractionalcoordinator.com ↗

Fractional Coordinator banner: five apps, one API, built from scratch, showing the marketing site, the client portal overview and the mobile site.
Fig. 01Site, portal and phone
Overview

The whole platform for a compliance firm, from the public site to the portal clients log into, all running on one API I designed and built.

Chapter 01 / 08The start

A firm outgrowing its tools

Fractional Coordinator runs Title IX, Title VI and ADA investigations for colleges, school districts and employers, and trains the people who do that work.

When I started, there was a public site in its own repo, a client portal that was a static shell of mock data, and signed contracts copied out of Google Drive by hand.

Chapter 02 / 08The goal

One platform the firm can run on

They needed a public site, a client portal, a console to run the firm and paid training. I built it from scratch, against three goals.

  1. Goal 1Each institution sees only its own work, and access ends the day an engagement does.
  2. Goal 2Staff publish, sell training and file contracts without a developer.
  3. Goal 3Every rule is written once and enforced the same way in every app.
Chapter 03 / 08Foundations

One codebase, one source of truth

Four audiences meant four front ends, with rules that overlap almost entirely. The first site had already shown what happens when they live apart: its form fields drifted from the API, and submissions were dropped in silence.

So everything moved into one Turborepo, sharing Zod contracts that turn that drift into a compile error. The rules live in one NestJS API, because half its callers, like Stripe and a Drive script, are not Next.js at all.

Trade‑offMore build plumbing, and a second deploy target to keep in step with the repo.

Intake API banner: the request a written quote page and the single forms route with its ten types and response codes.
Fig. 02One intake route for every form
Chapter 04 / 08Access

Access that fails closed

In a compliance portal, a leak is the worst possible bug. Grants hang off an institution membership, not the person, so removing a membership removes every grant with it.

Every API route must declare the grant it needs, and a route that declares nothing is refused, so forgetting a check locks a route instead of opening it. Sessions live in Postgres, so access ends the moment an engagement does, and there is no public sign‑up.

Trade‑offA membership lookup on every request, and a decorator on every route.

See the codeapps/api/src/database/schema/tenancy.schema.ts · portal/portal-grant.guard.ts

A grant row cascades with the membership it hangs off. The guard refuses a route that declares no grant at all, so forgetting the decorator locks a route instead of opening it.

organizationMemberId: text('organization_member_id')
  .notNull()
  .references(() => organizationMember.id, { onDelete: 'cascade' }),
grantKey: text('grant_key').notNull(),

// PortalGrantGuard.canActivate
if (required === undefined) {
  throw new ForbiddenException({
    code: 'GRANT_NOT_DECLARED',
    message: 'This route declares no entitlement',
  });
}
// ...
if (!membership.grants.includes(required)) {
  throw new ForbiddenException({ code: 'GRANT_REQUIRED', grant: required });
}

Read how entitlements work →

Client portal banner: the Documents tab for an institution, with engagement letters, reports and investigation files, and an action item card.
Fig. 03Client portal, scoped to one institution
Admin console banner: the institutions list with sector, lead and status, a view-as read-only bar and a staff assignment card.
Fig. 04Admin console with view‑as
Chapter 05 / 08Files

Files that never pass through the API

Case files are confidential, and some are large. They live in a private R2 bucket, reached only through links that expire after five minutes, so bytes never cross the API and a forwarded link soon stops working.

Signed contracts now file themselves. A script watches each institution’s Drive folder and sends new PDFs to the API. Each institution is bound to one folder, so even a leaked script secret cannot file into the wrong portal.

Trade‑offUp to ten minutes before a signed contract appears in the portal.

See the codeapps/api/src/signed-contracts/signed-contracts.service.ts

Every step of the sync re-checks the folder binding, and the file is read back before it is filed: right size, and it starts like a PDF.

private async assertLinked(organizationId: string, folderId: string): Promise<void> {
  const [org] = await this.db
    .select({ folderId: organization.driveContractsFolderId })
    .from(organization)
    .where(eq(organization.id, organizationId));

  if (!org || org.folderId === null || org.folderId !== folderId) {
    throw new NotFoundException();
  }
}

// register()
if (bytes.length !== input.byteSize) {
  return this.fail(input, 'The uploaded file is not the declared size.');
}
if (bytes.subarray(0, PDF_MAGIC.length).toString('latin1') !== PDF_MAGIC) {
  return this.fail(input, 'The uploaded file is not a PDF.');
}
Chapter 06 / 08Payments

Training that unlocks itself

Courses, Knowledge Hub memberships and Playbook licenses sell through Stripe Checkout. One webhook settles every purchase: it matches the buyer’s email to a portal member and unlocks access for their institution. There is no cart and no card data on our side.

Trade‑offA buyer without a portal account cannot be matched automatically, so those purchases land in a console list for staff.

Training and payments banner: the Learning Institute page, the Stripe checkout page and the webhook that grants a playbook license.
Fig. 05Checkout to access
Chapter 07 / 08The system

What it became

Five apps on their own subdomains, all calling one API that checks the session, the role and the institution before anything else happens.

Vercel Public site

Next.js and Sanity. Editors publish without a deploy.

Chapters03
Vercel Client portal · Console · Sign‑in

Three Next.js apps sharing UI, contracts and a session.

Chapters0304
→Session
cookie
Server · NestJS One API

Sessions, role guards, institution scoping and validation.

Chapters0304
Intake Forms and webhooks

Public forms, Stripe and the contract sync.

Chapters0506
→Server
only
Data Postgres

Institutions, members, cases, files and sessions.

Chapters04
Files Cloudflare R2

A public CDN and a private bucket.

Chapters05
Services Stripe · Resend · Google Drive · Cal.com

Payments, email, signed contracts and booking.

Chapters0506

Numbers mark the chapters behind each part. Point at one to see what it touches.

Chapter 08 / 08The end

Where it landed

The firm now runs its public site, client work, contracts and training on one platform. Back to the three goals:

  1. ✓ Goal 1
    Each institution sees only its own work.Every route checks its grant, and ending an engagement ends access at once.
  2. ✓ Goal 2
    Staff run it without a developer.Editors publish without a deploy, contracts file themselves and payments unlock access on their own.
  3. ✓ Goal 3
    Every rule is written once.Five apps share one API and one set of contracts.

Trade‑offs I live with

  • A separate API deploy that has drifted from the repo more than once.
  • Up to ten minutes before a signed contract shows up.
  • Buyers without an account still need a person to match them.

I shipped it in thirteen vertical slices, one pull request each, so every step left working software behind.

(06) Contact Open to new work

Let’s talk.

Need someone to own a feature from the database to the UI? I’m open to new roles and collaborations.

© 2026 Abdul Ahad Qureshi Set in Geist on a twelve‑column grid