本文へ移動
cccskills
無料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

インストール方法を見る

含まれるファイル(1)

  • SKILL.md6.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

Use when deciding whether a request needs an analiz task before implementation - the conditions that require the architect's analysis versus going straight to implementation

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1152026年10月17日 更新

makifbaysal のスキルをすべて見る

このスキルの問題を報告する