agentleFS
mmNalakammNalaka/nm-skills/skills/ts-api/SKILL.md

ts-api

Skill1 starsChanged 6 months agoMITView on GitHub

Use for TypeScript web API work — enforces strict types, Zod validation, DTOs, immutability, and feature-based file organization.

  • Reads credentials

The file

Show its rules and text

We split this skill into the rules it gives an agent

15 rules. About 0 are common, 7 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Prefer interface for object shapes, type for unions/intersections

    line 20
  2. Very specific:

    Use satisfies operator to validate objects against types without widening

    line 21
  3. Very specific:

    Never pass raw req.body to services — always pass the parsed, typed result

    line 28
  4. Distinctive:

    Never return DB model shapes directly — map to response DTOs

    line 57
  5. Distinctive:

    Never accept DB model shapes as function parameters in services

    line 58
  6. Very specific:

    No mutation — spread into new objects: const updated = { ...user, name: newName }

    line 87
  7. Very specific:

    Never use Object.assign(target, ...) where target is an existing object

    line 88
  8. Very specific:

    Prefer const everywhere; let only when reassignment is truly needed

    line 89
  9. Very specific:

    Never await inside a for / forEach loop — use Promise.all(items.map(...))

    line 94
  10. Very specific:

    Always await promises — never fire-and-forget unless explicitly intentional

    line 95
  11. Distinctive:

    Use a shared helper — never build the envelope inline in routes:

    line 125
  12. Distinctive:

    No business logic in routers

    line 139
  13. Distinctive:

    No DB queries in services

    line 140
  14. Distinctive:

    No mutation of existing objects

    line 141
  15. Distinctive:

    No raw error objects returned to clients (wrap them)

    line 143

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TypeScript Web API

Conventions for TypeScript/Node.js web API development. These apply for the entire session once activated.

Recommended model: Sonnet

Includes all standards from the api-design skill.


TypeScript Configuration

  • strict: true in tsconfig — no exceptions
  • Zero any — use unknown with type guards, or explicit typed generics
  • Prefer interface for object shapes, type for unions/intersections
  • Use satisfies operator to validate objects against types without widening

Validation

  • Zod for all boundary validation: request bodies, query params, route params, env vars
  • Parse at the entry point (route handler or middleware), not deep in services
  • Validate env vars at startup — use z.object({...}).parse(process.env) and fail fast
  • Never pass raw req.body to services — always pass the parsed, typed result
const schema = z.object({ email: z.string().email(), name: z.string().min(1) })
type CreateUserInput = z.infer<typeof schema>

Layer Separation

Three layers, each in its own file:

/users/
  users.router.ts     # HTTP: parse request, call service, return response
  users.service.ts    # Business logic: no HTTP, no DB queries
  users.repository.ts # DB: no business logic, raw data access
  users.schema.ts     # Zod schemas + inferred types
  users.types.ts      # DTOs and domain types

Router (thin): Parse input → call service → format response. No business logic.

Service: Business rules only. No req/res. No raw SQL. Returns domain types.

Repository: DB queries only. No business rules. Returns plain objects.

DTOs

  • Define explicit types for request input and response output
  • Never return DB model shapes directly — map to response DTOs
  • Never accept DB model shapes as function parameters in services
// types.ts
interface CreateUserInput { email: string; name: string }
interface UserResponse { id: string; email: string; name: string; createdAt: string }
// Never expose: passwordHash, internalFlags, etc.

Error Handling

  • Define typed error classes that extend Error:
class AppError extends Error {
  constructor(public code: string, message: string, public status: number) {
    super(message)
  }
}
class NotFoundError extends AppError {
  constructor(resource: string) { super('NOT_FOUND', `${resource} not found`, 404) }
}
  • Catch at the router/middleware layer only — services throw, routers catch
  • Map to the standard error envelope before responding

Immutability

  • No mutation — spread into new objects: const updated = { ...user, name: newName }
  • Never use Object.assign(target, ...) where target is an existing object
  • Prefer const everywhere; let only when reassignment is truly needed

Async Patterns

  • Promise.all([...]) for parallel independent async calls
  • Never await inside a for / forEach loop — use Promise.all(items.map(...))
  • Always await promises — never fire-and-forget unless explicitly intentional

Middleware

Standard middleware stack (applied in this order):

  1. Request ID injection (x-request-id header or generate UUID)
  2. Structured logging (log method, path, request ID)
  3. Auth JWT validation (skip public routes)
  4. Rate limit check
  5. Route handlers

File Organization

Feature-based, not type-based:

src/
  users/
  posts/
  auth/
  shared/
    errors.ts
    middleware.ts
    response.ts   # formatResponse(), formatError() helpers
  app.ts
  env.ts          # Zod-parsed env config

Response Helpers

Use a shared helper — never build the envelope inline in routes:

// shared/response.ts
export const ok = <T>(data: T, meta?: Meta): ApiResponse<T> => ({ data, error: null, meta })
export const fail = (code: string, message: string, status: number): never => {
  throw new AppError(code, message, status)
}

What NOT to do

  • No any — ever
  • No req.body passed directly to services
  • No business logic in routers
  • No DB queries in services
  • No mutation of existing objects
  • No await in loops
  • No raw error objects returned to clients (wrap them)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.