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
In this repository10 other files it gives its agents.
- api-designSkill
- brainstormSkill
- code-reviewSkill
- commitSkill
- evaluate-feedbackSkill
- go-apiSkill
- multi-agentSkill
- planSkill
- tddSkill
- workflowSkill
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
- Very specific: line 20
Prefer interface for object shapes, type for unions/intersections
- Very specific: line 21
Use satisfies operator to validate objects against types without widening
- Very specific: line 28
Never pass raw req.body to services — always pass the parsed, typed result
- Distinctive: line 57
Never return DB model shapes directly — map to response DTOs
- Distinctive: line 58
Never accept DB model shapes as function parameters in services
- Very specific: line 87
No mutation — spread into new objects: const updated = { ...user, name: newName }
- Very specific: line 88
Never use Object.assign(target, ...) where target is an existing object
- Very specific: line 89
Prefer const everywhere; let only when reassignment is truly needed
- Very specific: line 94
Never await inside a for / forEach loop — use Promise.all(items.map(...))
- Very specific: line 95
Always await promises — never fire-and-forget unless explicitly intentional
- Distinctive: line 125
Use a shared helper — never build the envelope inline in routes:
- Distinctive: line 139
No business logic in routers
- Distinctive: line 140
No DB queries in services
- Distinctive: line 141
No mutation of existing objects
- Distinctive: line 143
No raw error objects returned to clients (wrap them)
≈ “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.
Files
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: truein tsconfig — no exceptions- Zero
any— useunknownwith type guards, or explicit typed generics - Prefer
interfacefor object shapes,typefor unions/intersections - Use
satisfiesoperator 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.bodyto 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 typesRouter (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, ...)wheretargetis an existing object - Prefer
consteverywhere;letonly when reassignment is truly needed
Async Patterns
Promise.all([...])for parallel independent async calls- Never
awaitinside afor/forEachloop — usePromise.all(items.map(...)) - Always
awaitpromises — never fire-and-forget unless explicitly intentional
Middleware
Standard middleware stack (applied in this order):
- Request ID injection (
x-request-idheader or generate UUID) - Structured logging (log method, path, request ID)
- Auth JWT validation (skip public routes)
- Rate limit check
- 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 configResponse 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.bodypassed directly to services - No business logic in routers
- No DB queries in services
- No mutation of existing objects
- No
awaitin loops - No raw error objects returned to clients (wrap them)
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
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.

