Applied Module 12 · AI-Accelerated Government Development

Scaffold a New Module

What you'll learn

~30 min
  • Identify the 7 files every new feature module requires on the DS platform
  • Write a single prompt that generates all 7 files with correct conventions
  • Validate scaffolded output against platform patterns before committing

The 7-file tax

Every new feature module on the DS platform requires the same seven files before a single line of business logic gets written. You know the list by heart:

  1. Migration SQL (migrations/YYYYMMDD_create_<module>.sql) — the Azure SQL table definition with data_source, created_by, updated_at, and the audit columns your DBA expects.
  2. Types (src/types/<module>.ts) — Zod schemas for request/response validation, plus the TypeScript interfaces derived from them.
  3. API route handler (src/app/api/<module>/route.ts) — GET/POST/PUT/DELETE using the platform’s response helpers, with withPermission() guarding every method.
  4. SWR hook (src/hooks/use<Module>.ts) — useSWR for reads, useSWRMutation for writes, using the shared fetcher from swr-helpers.ts.
  5. Page component (src/app/(protected)/<module>/page.tsx) — the MUI 7 page wrapped in DensityGate, with loading skeleton and error boundary.
  6. Test file (src/tests/<module>.test.ts) — Vitest tests covering the API route handler and the Zod schemas.
  7. Styles (src/styles/<module>.module.css) — Tailwind v4 utilities composed into module-specific classes, following the CSS layer ordering the app shell established.

That is 45 minutes of boilerplate before you write a single conditional. And if you fat-finger a column name in the migration that does not match the Zod schema, you will not find out until a runtime error surfaces in staging.

💬This is where AI earns its keep

The 7-file scaffold is not interesting work. It is pattern replication — the exact kind of task where AI is faster, more consistent, and less error-prone than a human copying from the last module and doing find-and-replace. You still review every generated file. But the AI remembers every column naming convention, every import path, and every Zod refinement you need.


The prompt

This prompt assumes you are building a new module called vehicle-fleet — a fleet management tracker for state vehicle assignments. Adapt the module name and columns for your actual feature.

I need to scaffold a new feature module called "vehicle-fleet" for the DS platform.
Generate all 7 required files:
1. MIGRATION: migrations/20260320_create_vehicle_fleet.sql
- Table: vehicle_fleet
- Columns: id (INT IDENTITY PRIMARY KEY), vehicle_id (NVARCHAR(20) NOT NULL),
make (NVARCHAR(50)), model (NVARCHAR(50)), year (INT),
assigned_to (NVARCHAR(100)), assignment_date (DATE),
status (NVARCHAR(20) DEFAULT 'active'),
data_source (NVARCHAR(20) NOT NULL DEFAULT 'manual'),
created_by (NVARCHAR(100) NOT NULL), created_at (DATETIME2 DEFAULT GETDATE()),
updated_by (NVARCHAR(100)), updated_at (DATETIME2)
- Include the UPDATE trigger for updated_at
- Azure SQL syntax (not Postgres)
2. TYPES: src/types/vehicle-fleet.ts
- Zod schemas: VehicleFleetCreateSchema (for POST body validation),
VehicleFleetUpdateSchema (partial of Create, plus id required),
VehicleFleetRow (full DB row shape including audit columns)
- Export inferred TypeScript types from each schema
- Import z from 'zod' (Zod v3, not v4)
3. API ROUTE: src/app/api/vehicle-fleet/route.ts
- GET: listResponse() with 500-item pagination cap, supports ?status= filter
- POST: validate body with VehicleFleetCreateSchema, singleResponse()
- PUT: validate body with VehicleFleetUpdateSchema, singleResponse()
- DELETE: deletedResponse(), soft-delete by setting status='inactive'
- Every method wrapped in withPermission('vehicle-fleet', method)
- Use the platform response helpers from '@/lib/response-helpers'
- Use getDbPool() from '@/lib/db' for database access
- Never leak stack traces -- errorResponse() for all caught errors
4. SWR HOOK: src/hooks/useVehicleFleet.ts
- useSWR for the list endpoint using fetcher from '@/lib/swr-helpers'
- useSWRMutation for create, update, delete
- Return { data, error, isLoading, create, update, remove, mutate }
5. PAGE: src/app/(protected)/vehicle-fleet/page.tsx
- 'use client' directive
- Import useVehicleFleet hook
- MUI DataGrid for the table with sortable columns
- Wrap content in DensityGate with executive/operational/technical views
- Loading skeleton while data fetches
- MUI Snackbar for mutation feedback (success and error)
- Add/Edit dialog using MUI Dialog
6. TESTS: src/tests/vehicle-fleet.test.ts
- Vitest tests for the Zod schemas (valid data passes, invalid rejects)
- Test the API route handler GET/POST/PUT/DELETE with mocked DB
- At least 8 test cases
7. STYLES: src/styles/vehicle-fleet.module.css
- @layer components for module-specific styles
- Status badge styles (active=green, inactive=gray, maintenance=amber)
- Responsive table container
Follow existing platform conventions exactly. Use the same import paths,
the same response helper signatures, the same SWR patterns as existing modules.
💡Adapt the columns, keep the structure

The prompt above uses vehicle-fleet as an example. When you scaffold your own module, replace the table name, columns, and Zod fields — but keep the 7-file structure and the platform conventions (response helpers, withPermission(), data_source column, swr-helpers fetcher) exactly as shown. Those are non-negotiable.


Watch it work

Claude Code — Scaffolding 7 module files
/home/user $ claude
/home/user $

What to check before you commit

AI-generated scaffolds are consistent, but they are not infallible. Run through this checklist on every scaffold:

CheckWhat to look for
Column names matchThe migration SQL column names must exactly match the Zod schema field names (snake_case in SQL, camelCase in Zod with .transform() or matching keys). A mismatch here is a silent runtime bug.
Zod versionConfirm import { z } from 'zod' — not import z from 'zod/v4'. The platform uses Zod v3 for stability.
Response helpers importedlistResponse, singleResponse, errorResponse, deletedResponse from @/lib/response-helpers. Not hand-rolled JSON responses.
withPermission wraps every methodIf the AI generated a raw export async function GET() without withPermission(), the route is unguarded. That is a security finding.
data_source column presentEvery table needs it. If the AI omitted it, the provenance system cannot track the module’s data.
500-item pagination capThe GET handler must enforce OFFSET/FETCH NEXT with a max of 500 rows. No unlimited result sets.
SWR fetcher sourceThe hook must use the shared fetcher from @/lib/swr-helpers, not a local fetch() wrapper.
Test file runsnpx vitest run src/tests/vehicle-fleet.test.ts — if it fails, fix the test before you fix the code.
Do not skip the column-name check

The most common scaffold bug is a column name mismatch between the SQL migration and the Zod schema. The migration uses assignment_date but the Zod schema uses assignmentDate without a transform — and the API silently drops the field. Confirm the mapping before you run the migration.


Why 7 files and not 3

If you are coming from a simpler stack, 7 files per feature feels excessive. Here is why each one exists:

  • Migration separate from types — the DBA reviews SQL migrations independently. They do not read TypeScript.
  • Types separate from API route — Zod schemas are reused by the SWR hook (for optimistic update validation) and by tests. Coupling them to the route handler makes reuse impossible.
  • SWR hook separate from page — multiple pages may consume the same data (the fleet list page, an executive dashboard, an admin view). The hook is the shared data layer.
  • Tests separate from everything — state audit requirements. Test coverage is reported per-module. If the test file does not exist, the module fails the audit gate in the pipeline.
  • Styles in a CSS module — Tailwind v4 with MUI 7 requires careful layer ordering. Module-scoped CSS prevents style collisions across features.

This structure is not bureaucracy. It is the result of a team learning what breaks when you take shortcuts in a codebase that multiple agencies depend on.


KNOWLEDGE CHECK

You scaffold a new module and run the tests. The POST test fails with 'Expected status 201, received 403.' All other tests pass. What is the most likely cause?


What’s next

Your 7 files exist, but the platform does not know about them yet. A module that is not registered in the RBAC types, permission matrix, nav routes, provenance map, and module status enum is invisible — or worse, it silently fails when a user with the wrong role stumbles into an unguarded route. In the next lesson, you will register the module in all 5 places with a single prompt.