Skip to content
cccskills
FreePublished on GitHub

node-typescript-service

Use when building or extending a TypeScript service on Node.js - Fastify, NestJS, Express or Hono, runtime validation at the boundary, strict types, the repository's ORM and migrations, async error handling, graceful shutdown

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md6.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

TypeScript Service on Node.js

Overview

A Node.js service is TypeScript at compile time and untyped JavaScript at run time. Every type at the edge — a request body, a header, an environment variable, a queue message, a row from a raw query — is a claim until something checks it.

Core principle: the repository's framework, package manager, module system and ORM win. Parse input at the boundary into typed values; keep the domain free of the framework.

Choosing, only when there is nothing to follow

  • Fastify for a new HTTP service: schema-first routes, app.inject() for tests, pino logging built in.
  • NestJS when the service is domain-rich and the team wants modules, DI and decorators (the Quarkus/Spring of Node).
  • Express 5 or Hono only to match a repository or a platform that already uses them.

Layers

LayerResponsibility
Route / controllerParse and validate input, call one service, map the result and errors to HTTP
ServiceUse-case orchestration and business rules; takes plain typed values
DomainTypes, invariants, pure functions — no framework import
AdapterDatabase, outbound HTTP, queues — behind an interface the service depends on

Patterns

  • Package manager from the lockfile: pnpm-lock.yaml → pnpm, yarn.lock → yarn, package-lock.json → npm; install with the frozen form (pnpm install --frozen-lockfile, npm ci). Node version from .nvmrc, .node-version or engines; a new service targets the current Active LTS.
  • Module system from package.json: "type": "module" means ESM (relative imports carry .js under NodeNext); otherwise CommonJS. Never mix them in one package.
  • strict: true. No any; unknown at the boundary, narrowed by a parser. No non-null ! to silence a real undefined.
  • Validate at runtime at the boundary: zod (or TypeBox / Fastify JSON schema, class-validator in NestJS — whatever the repository uses) on body, params, query and headers; derive the TypeScript type from the schema (z.infer) so the two cannot drift. Environment variables are parsed once at startup by the same means and the process exits on a bad one.
  • Errors in one shape: a central error handler (setErrorHandler, a Nest exception filter, Express 5's error middleware) maps domain errors to 400/404/409 problem+json; an unknown error is logged with its stack and answered 500 without it.
  • No floating promises: every promise is awaited, returned or explicitly handled (@typescript-eslint/no-floating-promises catches the rest). An unhandled rejection crashes the process in current Node.
  • Outbound calls have a deadline: fetch(url, { signal: AbortSignal.timeout(5_000) }) or the client's own timeout; retries only for idempotent calls (resilient-io-and-jobs).
  • Never block the event loop: no *Sync file or crypto calls on a request path, no unbounded JSON.parse of a large body, CPU-heavy work in a worker thread or a job.
  • Database: the repository's tool — Prisma, Drizzle, Kysely, TypeORM, MikroORM or pg — with parameters, never string-built SQL ($queryRawUnsafe and sql.raw with input are bugs). Migrations through that tool (prisma migrate dev --name, drizzle-kit generate), committed with the schema change; postgres-migrations covers safe DDL.
  • Logging: pino (Fastify's logger, nestjs-pino) with structured fields, request id bound per request; no console.log in service code; redact secrets and tokens (redact option).
  • Graceful shutdown: on SIGTERM stop accepting connections, finish in-flight requests, close the pool, then exit — Fastify close(), Nest enableShutdownHooks().
  • Dependencies: a new package is a decision (reuse-before-you-write) — exact name from the registry, added with the repository's package manager, lockfile in the same commit.

Worked Example

POST /tasks in Fastify with zod:

const CreateTask = z.object({ title: z.string().trim().min(1).max(200) });

export function taskRoutes(app: FastifyInstance, service: TaskService) {
  app.post("/tasks", async (req, reply) => {
    const parsed = CreateTask.safeParse(req.body);
    if (!parsed.success) {
      return reply.code(400).type("application/problem+json").send(problemFrom(parsed.error));
    }
    const task = await service.create(parsed.data.title, req.user.id);
    return reply.code(201).header("location", `/tasks/${task.id}`).send(toResponse(task));
  });
}

TaskService.create takes a string and an owner id and knows nothing of Fastify; Task.create enforces the 200-character rule again for callers that are not HTTP.

Verify

The repository's scripts first (list_component_checks, package.json, CI): typically npm run typecheck (or tsc --noEmit), npm run lint, npm test, npm run build. For the runtime check build once and run the output — npm run build, then PORT=18080 node dist/main.js in the background with its pid recorded — not a watcher (npm run dev, tsx watch, nodemon), which never exits and restarts under you.

Common Mistakes

  • Trusting req.body as CreateTaskDto — a cast is not a check.
  • async route handlers in Express 4 without a wrapper: the rejection never reaches the error middleware.
  • A request path that awaits in a for loop where one query or Promise.all with a bound would do — or Promise.all over an unbounded list.
  • process.env.X! scattered through the code instead of one parsed config object.

Red Flags

  • any in a new signature, or // @ts-ignore / @ts-expect-error added to get a build green.
  • String-concatenated SQL, or a raw query helper fed user input.
  • A promise created and never awaited in a request handler.
  • console.log of a request body that may hold a token or a password.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

See all skills by makifbaysal

Report a problem with this skill