SuperAPI AI Directory

typescript-full-stack

Coding & development

View repository

Updated 10-16 06:27

About this skill

Full-Stack TypeScript

Acknowledgement: Shared by Peter Bamuhigire, techguypeter.com, +256 784 464178.

Use When

  • Use when building end-to-end TypeScript applications — Node backend (Fastify), React/Next frontend, shared types via tRPC or Zod, monorepo with turborepo/nx, Prisma/Drizzle data layer, end-to-end type safety.

Evidence Produced

Category Artifact Format Example
Correctness Full-stack contract test plan Markdown doc covering Fastify route + tRPC router + Prisma schema contract tests docs/ts/full-stack-tests.md

References

  • Use the references/ directory for deep detail after reading the core workflow below.

Build and maintain apps where the same types flow from database to backend to frontend to mobile. Zero runtime type drift between layers.

Prerequisites: Load typescript-effective for production idioms. Load typescript-mastery for type-system depth.

When this skill applies

  • Greenfield TypeScript web app (React + Node).
  • Unifying a split JS frontend + JS backend under TypeScript.
  • Refactoring an Express/JS backend into a typed Fastify/TS backend while keeping the frontend running.
  • Setting up a monorepo with shared types across packages.

Architecture baseline

+----------------------------------+
|  apps/web         (Next.js)      |
|  apps/mobile      (Expo RN)      |
|  apps/api         (Fastify)      |
|  packages/schemas (Zod)          |---> source of truth for types
|  packages/api-client (tRPC / fetch)
|  packages/db      (Prisma client)
|  packages/ui      (shadcn-like components)
|  packages/config  (tsconfig, eslint, prettier)
+----------------------------------+
  • Zod schemas are the single source of truth; TS types are inferred.
  • tRPC for internal APIs (no OpenAPI needed — types flow).
  • REST + OpenAPI only when external clients consume the API. Use ts-rest or zod-to-openapi.
  • Prisma for SQL-first data layer; Drizzle when you want SQL in view and finer control.
  • Auth via Lucia, better-auth, or Clerk — not rolling your own.

Monorepo with turborepo

pnpm init -w
pnpm add -D turbo

turbo.json defines pipeline (build, test, lint, type-check) with caching. Remote cache on Vercel or self-hosted.

Rule: every package has its own tsconfig.json extending packages/config/tsconfig.base.json. Project references enable incremental typecheck.

See references/monorepo-turborepo.md.

Fastify backend (TS-first)

import Fastify from "fastify";
import { z } from "zod";

const app = Fastify({ logger: true });

const Params = z.object({ id: z.string().uuid() });

app.get("/users/:id", async (req, reply) => {
  const { id } = Params.parse(req.params);
  const user = await db.user.findUnique({ where: { id } });
  if (!user) return reply.code(404).send({ error: "not_found" });
  return user;
});

Fastify beats Express for TS: first-class hooks, schema validation, plugin system, lifecycle typing. See references/fastify-backend.md.

tRPC — end-to-end types without OpenAPI

// packages/api/src/router.ts
import { initTRPC } from "@trpc/server";
import { z } from "zod";

const t = initTRPC.create();

export const appRouter = t.router({
  user: t.router({
    byId: t.procedure
      .input(z.object({ id: z.string().uuid() }))
      .query(async ({ input }) => db.user.findUnique({ where: { id: input.id } })),
    create: t.procedure
      .input(UserCreate)
      .mutation(async ({ input }) => db.user.create({ data: input })),
  }),
});

export type AppRouter = typeof appRouter;

Frontend imports AppRouter type only. Runtime is fetch; types flow at compile time. Zero duplication. See references/trpc-end-to-end.md.

When tRPC vs REST:

All clients are your own code, no public API            -> tRPC
External partners, third-party integrations, public SDK -> REST + OpenAPI
Mobile on old TS versions / no TS                       -> REST
GraphQL ecosystem in the org                            -> GraphQL (not covered here)

REST + OpenAPI (when needed)

Use ts-rest or zod-to-openapi — define schemas once, generate OpenAPI spec automatically.

import { initContract } from "@ts-rest/core";

const c = initContract();

export const userContract = c.router({
  getUser: {
    method: "GET",
    path: "/users/:id",
    pathParams: z.object({ id: z.string().uuid() }),
    responses: { 200: UserSchema, 404: z.object({ error: z.literal("not_found") }) },
  },
});

See references/rest-plus-openapi.md.

Data layer — Prisma or Drizzle

Prisma: schema-first, Prisma Client is fully typed, great DX. Ships with migration tool. Best for teams.

Drizzle: SQL-first, TypeScript-first. Queries look like SQL, no hidden abstractions. Best when you love SQL and want explicit control.

Decision rule:

Want schema.prisma as source of truth, good migrations, team of mixed SQL skill -> Prisma
Want SQL-in-code visibility, complex queries, performance-critical               -> Drizzle
Postgres-only with advanced features (arrays, ranges, custom types)              -> Drizzle
MySQL primary, migrations managed alongside PHP app                              -> Prisma

See references/prisma-vs-drizzle.md.

Zod schemas as the shared layer

// packages/schemas/src/user.ts
import { z } from "zod";

export const UserCreate = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  role: z.enum(["admin", "member", "viewer"]),
}).strict();

export const User = UserCreate.extend({
  id: z.string().uuid(),
  createdAt: z.coerce.date(),
});

export type UserCreate = z.infer<typeof UserCreate>;
export type User = z.infer<typeof User>;

Consumed by: API validation, Prisma custom types, React Hook Form, tRPC input, test fixtures. See references/zod-shared-schemas.md.

Auth

  • Lucia — session-based, database-backed, flexible. Good for first-party apps.
  • better-auth — newer, feature-rich (MFA, OAuth, magic links).
  • Clerk — hosted auth; trade cost for speed. Great for MVPs.
  • NextAuth/Auth.js — incumbent; fine but DX can be rough.

Never roll password hashing, session storage, or OAuth flows yourself. See references/auth-patterns.md.

Testing the full stack

  • Unit: vitest in each package.
  • Integration: vitest + test database (Testcontainers or pg-lite).
  • E2E: Playwright for the web app.
  • Contract tests: if you expose REST, snapshot the OpenAPI spec — breaking changes fail CI.
  • Type tests: expectTypeOf for API client usage.

See references/testing-full-stack.md.

Deployment — Docker for Node

Multi-stage Dockerfile, node-slim base, non-root user, pnpm deploy for prod-only deps:

FROM node:22-slim AS base
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app

FROM base AS deps
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/api/package.json ./apps/api/
COPY packages/ ./packages/
RUN pnpm install --frozen-lockfile

FROM deps AS build
COPY . .
RUN pnpm --filter api build

FROM base AS prod
ENV NODE_ENV=production
USER node
WORKDIR /app
COPY --from=build --chown=node:node /app/apps/api/dist ./dist
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

See references/docker-node-production.md.

Anti-patterns

  • Duplicating types between frontend and backend ("I'll just type it again here").
  • Trusting API responses without Zod validation on the client.
  • Running migrations in app startup — run in CI or a separate migrate step.
  • Sharing Prisma client across edge + Node runtime without a driver adapter.
  • Importing server code into client code (mark server-only with "use server" or a runtime guard).
  • Monorepo without remote build cache on CI — slow feedback.
  • any on API boundaries.

Read next

  • typescript-effective — idioms.
  • typescript-mastery — type system depth.
  • react-development / nextjs-app-router — frontend.
  • nodejs-development — Node runtime patterns.
  • postgresql-fundamentals / mysql-best-practices — data layer specifics.

References

  • references/monorepo-turborepo.md
  • references/fastify-backend.md
  • references/trpc-end-to-end.md
  • references/rest-plus-openapi.md
  • references/prisma-vs-drizzle.md
  • references/zod-shared-schemas.md
  • references/auth-patterns.md
  • references/testing-full-stack.md
  • references/docker-node-production.md
  • references/large-scale-react-ts.md - load when designing folder structure, state ownership, HTTP/API client seams, config, i18n, primitives, or test layout for a large React + TS app.

Decision Rules

Condition Action
Only first-party TypeScript clients consume API Consider tRPC with runtime validation
External clients or long-lived integrations exist Publish REST/OpenAPI contracts
Shared package creates circular dependencies Split schemas from implementations

Capability Contract

Read and search are required. Editing, database mutation, builds, and tests require authorisation.

Degraded Mode

Fallback: without execution or services, return unverified integration points and exact end-to-end checks. Validate every network boundary at runtime.

Inputs

Artefact Required? Purpose
Frontend/backend boundaries, shared contracts, runtime targets, and deployment context yes Coordinate full-stack types and behaviour

Outputs

  • Produce end-to-end TypeScript implementation or design with shared contracts, tests, and deployment notes.