| name | api-design-advanced |
| description | Advanced API design — per-language implementation patterns (TypeScript/Next.js, Go/net-http), anti-patterns (200 for everything, 500 for validation, contract breaking), and full pre-ship checklist. |
API Design — Advanced Patterns
This skill extends api-design with implementation patterns and anti-patterns. Load api-design first.
When to Activate
- Implementing API handlers in TypeScript (Next.js) or Go
- Reviewing code for common API anti-patterns
- Applying the full pre-ship checklist to a new endpoint
Implementation Patterns
TypeScript (Next.js API Route)
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
const PROBLEM_CONTENT_TYPE = "application/problem+json";
export async function POST(req: NextRequest) {
const body = await req.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({
type: "https://api.example.com/problems/validation-failed",
title: "Validation Failed",
status: 422,
detail: "One or more fields failed validation.",
errors: parsed.error.issues.map(i => ({ field: i.path.join("."), detail: i.message })),
}, { status: 422, headers: { "Content-Type": PROBLEM_CONTENT_TYPE } });
}
const user = await createUser(parsed.data);
return NextResponse.json(
{ data: user },
{ status: 201, headers: { Location: `/api/v1/users/${user.id}` } },
);
}
Go (net/http)
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeProblem(w, r, http.StatusBadRequest,
"https://api.example.com/problems/bad-request", "Bad Request",
"Invalid request body.")
return
}
if err := req.Validate(); err != nil {
writeProblem(w, r, http.StatusUnprocessableEntity,
"https://api.example.com/problems/validation-failed", "Validation Failed",
err.Error())
return
}
user, err := h.service.Create(r.Context(), req)
if err != nil {
switch {
case errors.Is(err, domain.ErrEmailTaken):
writeProblem(w, r, http.StatusConflict,
"https://api.example.com/problems/email-taken", "Email Taken",
"This email is already registered.")
default:
writeProblem(w, r, http.StatusInternalServerError,
"about:blank", "Internal Server Error", "")
}
return
}
w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]any{"data": user})
}
func writeProblem {
w.Header().Set(, )
w.WriteHeader(status)
json.NewEncoder(w).Encode([]any{
: type_, : title, : status,
: detail, : r.RequestURI,
})
}
Anti-Patterns
Using 200 OK for Every Response
Wrong:
HTTP/1.1 200 OK
Content-Type: application/json
{ "success": false, "error": "User not found" }
Correct:
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/not-found",
"title": "Not Found",
"status": 404,
"detail": "User abc-123 not found.",
"instance": "/api/v1/users/abc-123"
}
Why: Returning 200 for errors forces clients to parse the body to detect failure, breaks HTTP caches and load balancers, and defeats the purpose of the status code system.
Returning 500 for Validation Errors
Wrong:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{ "error": "Invalid email format" }
Correct:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-failed",
"title": "Validation Failed",
"status": 422,
"errors": [
{ "field": "email", "detail": "must be a valid email address" }
]
}
Why: Validation errors are client mistakes (4xx), not server failures (5xx) — misusing 500 triggers false alerts, hides real server errors, and confuses API consumers.
Breaking the API Contract Without a Version Bump
Wrong:
responses:
'200':
content:
application/json:
schema:
properties:
full_name: { type: string }
Correct:
Sunset: Sat, 01 Jan 2027 00:00:00 GMT
Why: Renaming, removing, or retyping fields in an existing version silently breaks all existing clients; any breaking change must be introduced under a new API version.
API Design Checklist
Before shipping a new endpoint:
Reference
api-design — core REST patterns (resource naming, status codes, response format, auth, rate limiting, versioning)
api-contract — Contract-First workflow (OpenAPI spec, code generation, CI breaking-change detection)