Rest api node
Skill VersoXBT/claude-initial-setup/skills/express-node/rest-api-node
RESTful API design conventions for Node.js including resource naming, pagination, filtering, sorting, HATEOAS links, versioning, and content negotiation. Use when the user is designing REST APIs, asking about pagination or filtering patterns, implementing API versioning, or building CRUD endpoints. Trigger on mentions of REST API design, API pagination, query parameters, HATEOAS, API versioning, or RESTful conventions.From its SKILL.md
npx -y skills add VersoXBT/claude-initial-setup --skill rest-api-nodeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.1 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
RESTful API Design for Node.js
Conventions and patterns for designing consistent, scalable REST APIs.
When to Use
- User is designing REST API endpoints
- User needs pagination, filtering, or sorting
- User asks about API versioning strategies
- User wants consistent response formats
- User mentions HATEOAS or API discoverability
Core Patterns
Resource Naming Conventions
Use plural nouns for collections. Nest sub-resources to express relationships. Keep URLs shallow (max 2 levels of nesting).
GET /api/v1/users -- List users
POST /api/v1/users -- Create user
GET /api/v1/users/:id -- Get user
PUT /api/v1/users/:id -- Replace user
PATCH /api/v1/users/:id -- Partial update
DELETE /api/v1/users/:id -- Delete user
GET /api/v1/users/:id/orders -- List user's orders
POST /api/v1/users/:id/orders -- Create order for user
-- Actions that don't map to CRUD use verbs as sub-resources
POST /api/v1/users/:id/activate
POST /api/v1/orders/:id/cancel
Pagination
Return paginated results with metadata. Support both offset-based and cursor-based pagination.
import { Request, Response } from 'express'
interface PaginationQuery {
page?: string
limit?: string
cursor?: string
}
async function listUsers(req: Request, res: Response) {
const page = Math.max(1, parseInt(req.query.page as string) || 1)
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit as string) || 20))
const offset = (page - 1) * limit
const [users, total] = await Promise.all([
db.user.findMany({ skip: offset, take: limit, orderBy: { createdAt: 'desc' } }),
db.user.count(),
])
const totalPages = Math.ceil(total / limit)
const baseUrl = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`
res.json({
data: users,
meta: { page, limit, total, totalPages },
links: {
self: `${baseUrl}?page=${page}&limit=${limit}`,
first: `${baseUrl}?page=1&limit=${limit}`,
last: `${baseUrl}?page=${totalPages}&limit=${limit}`,
...(page > 1 && { prev: `${baseUrl}?page=${page - 1}&limit=${limit}` }),
...(page < totalPages && { next: `${baseUrl}?page=${page + 1}&limit=${limit}` }),
},
})
}
Filtering and Sorting
Accept filters and sort via query parameters. Validate allowed fields.
const ALLOWED_FILTERS = new Set(['status', 'role', 'createdAfter', 'createdBefore'])
const ALLOWED_SORT_FIELDS = new Set(['name', 'email', 'createdAt'])
function parseFilters(query: Record<string, string>) {
const where: Record<string, unknown> = {}
if (query.status && ALLOWED_FILTERS.has('status')) {
where.status = query.status
}
if (query.role && ALLOWED_FILTERS.has('role')) {
where.role = query.role
}
if (query.createdAfter) {
where.createdAt = { ...(where.createdAt as object), gte: new Date(query.createdAfter) }
}
if (query.createdBefore) {
where.createdAt = { ...(where.createdAt as object), lte: new Date(query.createdBefore) }
}
return where
}
function parseSortParam(sort: string | undefined) {
if (!sort) return { createdAt: 'desc' as const }
const desc = sort.startsWith('-')
const field = desc ? sort.slice(1) : sort
if (!ALLOWED_SORT_FIELDS.has(field)) return { createdAt: 'desc' as const }
return { [field]: desc ? 'desc' : 'asc' }
}
// Usage: GET /api/v1/users?status=active&sort=-createdAt&page=2
router.get('/users', asyncHandler(async (req, res) => {
const where = parseFilters(req.query as Record<string, string>)
const orderBy = parseSortParam(req.query.sort as string)
// ... paginate with where and orderBy
}))
API Versioning
Use URL path versioning for simplicity and clarity. Each version is an explicit contract.
import { Router } from 'express'
const v1Router = Router()
v1Router.use('/users', userRoutesV1)
v1Router.use('/orders', orderRoutesV1)
const v2Router = Router()
v2Router.use('/users', userRoutesV2)
v2Router.use('/orders', orderRoutesV2)
app.use('/api/v1', v1Router)
app.use('/api/v2', v2Router)
Consistent Response Format
Use a uniform response envelope across all endpoints.
interface ApiResponse<T> {
data: T
meta?: {
page: number
limit: number
total: number
totalPages: number
}
links?: Record<string, string>
}
interface ApiErrorResponse {
error: string
details?: Record<string, string[]>
}
// Success
res.status(200).json({ data: user })
// Created
res.status(201).json({ data: newUser })
// No Content (delete)
res.status(204).end()
// Error
res.status(400).json({ error: 'Validation failed', details: { email: ['Invalid'] } })
Content Negotiation
Respond in the format the client requests via the Accept header.
function negotiateResponse(req: Request, res: Response, data: unknown) {
res.format({
'application/json': () => res.json({ data }),
'text/csv': () => {
const csv = convertToCsv(data)
res.type('text/csv').send(csv)
},
default: () => res.status(406).json({ error: 'Not Acceptable' }),
})
}
Anti-Patterns
- Using verbs in resource URLs --
/api/getUsersviolates REST conventions. UseGET /api/usersinstead. HTTP methods convey the action. - Returning 200 for errors -- Always use appropriate HTTP status codes. 200 means success. Use 4xx for client errors, 5xx for server errors.
- Unbounded list endpoints -- Always paginate collection endpoints. Returning all records causes memory exhaustion and slow responses.
- Exposing database IDs or internal structure -- Use UUIDs instead of sequential IDs. Do not leak table names or column names in error messages.
- Inconsistent response formats -- Sometimes returning
{ user: ... }, sometimes{ data: ... }. Pick one envelope format and use it everywhere.
Quick Reference
HTTP Methods:
GET -- Read (idempotent, safe)
POST -- Create (not idempotent)
PUT -- Replace (idempotent)
PATCH -- Partial update (idempotent)
DELETE -- Remove (idempotent)
Status Codes:
200 OK -- Success
201 Created -- Resource created
204 No Content -- Successful delete
400 Bad Request -- Validation error
401 Unauthorized -- Missing/invalid auth
403 Forbidden -- Insufficient permissions
404 Not Found -- Resource doesn't exist
409 Conflict -- Duplicate/conflict
429 Too Many Reqs -- Rate limited
500 Internal Error -- Server bug
Query patterns:
?page=2&limit=20 -- Pagination
?sort=-createdAt -- Sort desc
?status=active&role=admin -- Filters
?fields=id,name,email -- Sparse fields
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most apis services skills give in ~1.7k tokens
Counted across 448 of the 471 authors here whose files we hold, read 2026-09-06
- Use HTTP status codes semanticallyin 25 of 448, across 11 files
- Return 201 with a Location header on createin 24 of 448, across 9 files
- Name resources plural, lowercase, kebab-casein 23 of 448, across 9 files
- Configure rate limiting with limit headersin 22 of 448, across 8 files
- Paginate list endpoints with cursor or offsetin 21 of 448, across 10 files
- Version APIs in the URL pathin 21 of 448, across 11 files
- Validate request input with a schemain 21 of 448, across 7 files
- Add pagination to all list endpointsin 18 of 448, across 15 files
- Match HTTP method to the operationin 12 of 448, across 6 files
- Return 400 or 422 with field-level detailsin 12 of 448, across 2 files
- Check ownership before returning resourcesin 12 of 448, across 2 files
- Limit query depth and complexityin 12 of 448, across 7 files
Said here and by no other author read
- use verbs as sub-resources for non-CRUD actions
- validate allowed filter and sort fields
- use a uniform response envelope
- respond in the format the client requests
- use UUIDs instead of sequential IDs
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.