API Development Best Practices 2026: REST, GraphQL, and the Rise of tRPC
The API Landscape in 2026
APIs are the invisible infrastructure of the modern web. Every mobile app, SaaS product, and web application depends on APIs to exchange data. Building APIs well — for performance, security, reliability, and developer experience — is one of the most impactful software engineering skills.
In 2026, the API landscape has evolved: REST remains dominant, GraphQL has matured into a niche (but powerful) tool, and type-safe RPC frameworks like tRPC have gained significant traction in full-stack TypeScript projects.
REST: Still the Right Default
Despite regular predictions of its death, REST (Representational State Transfer) remains the default API style for good reasons:
- Universally understood: Any developer from any background understands HTTP methods and status codes
- Excellent tooling: Postman, OpenAPI/Swagger, REST clients for every language
- HTTP-native caching: Browser and CDN caching work naturally with REST's resource model
- Simple to debug: HTTP logs, curl commands, browser DevTools — debugging REST APIs requires no special tools
REST Best Practices in 2026
Resource naming: Use nouns, not verbs. /api/users not /api/getUsers. Use plural nouns. Nest resources to express relationships: /api/users/{id}/orders.
HTTP methods:
- GET: Retrieve (must be idempotent, never modify data)
- POST: Create new resource
- PUT: Replace entire resource
- PATCH: Update specific fields of resource
- DELETE: Remove resource
Status codes (use them correctly):
- 200: Success with body
- 201: Resource created successfully (include Location header)
- 204: Success with no body (delete operations)
- 400: Client error (validation failed — include details)
- 401: Not authenticated
- 403: Authenticated but not authorized
- 404: Resource not found
- 409: Conflict (duplicate resource, version conflict)
- 422: Unprocessable entity (valid format but semantic errors)
- 429: Rate limit exceeded (include Retry-After header)
- 500: Server error (never expose stack traces)
Consistent error responses:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided email address is invalid",
"field": "email"
}
}
GraphQL: Powerful but Niche
GraphQL solves specific problems that REST doesn't handle well: over-fetching, under-fetching, and rapidly evolving frontend requirements where the backend API shouldn't be the bottleneck.
Choose GraphQL when:
- Multiple clients (mobile, web, third-party) need different data shapes from the same resources
- Your data is genuinely graph-like (social networks, complex relational data)
- Rapid frontend iteration is more important than backend API stability
Don't choose GraphQL when:
- Your API is primarily for internal use by a single client
- Your team isn't experienced with GraphQL's complexity (N+1 problems, authorization in resolvers, schema complexity)
- You need simple HTTP caching (GraphQL typically uses POST requests, bypassing HTTP cache)
- Your API is primarily file uploads or streaming data
GraphQL's complexity is real: batching/caching (DataLoader), authorization at the field level, schema federation for microservices — each adds significant operational complexity. Justify it with clear requirements, not hype.
tRPC: Type-Safe APIs for TypeScript Full-Stack
tRPC is the biggest API innovation of recent years for TypeScript full-stack applications. It provides end-to-end type safety between your Next.js/React frontend and Node.js backend — without code generation, without a schema definition language, without REST or GraphQL.
// Backend (server)
const appRouter = router({
getUser: publicProcedure
.input(z.string())
.query(async ({ input: userId }) => {
return await db.user.findUnique({ where: { id: userId } });
}),
createPost: protectedProcedure
.input(z.object({ title: z.string(), content: z.string() }))
.mutation(async ({ input, ctx }) => {
return await db.post.create({
data: { ...input, authorId: ctx.user.id }
});
}),
});
// Frontend (client) — fully typed, no boilerplate
const user = await trpc.getUser.query('user_123');
// TypeScript knows the exact return type automatically
Choose tRPC when: You're building a full-stack TypeScript app with Next.js and Node.js backend. The developer experience improvement is substantial.
Don't choose tRPC when: You need to expose APIs to external clients (tRPC is TypeScript-specific), you have a mixed-language team, or you're building a public API.
API Security (Non-Negotiable)
Authentication
JWT (JSON Web Tokens): The standard for stateless API authentication. Key practices:
- Use short expiry times for access tokens (15 minutes to 1 hour)
- Use long-lived refresh tokens (7–30 days) stored in httpOnly cookies
- Sign JWTs with RS256 (asymmetric) for distributed services; HS256 is fine for monoliths
- Include only necessary claims — JWTs are base64-encoded, not encrypted
API Keys: For machine-to-machine communication. Store hashed (bcrypt) in database. Display full key only once at creation.
OAuth 2.0 + OIDC: For user authentication and third-party access. Use an identity provider (Auth0, Clerk, Supabase Auth) rather than implementing OAuth yourself.
Authorization
Authentication ("who are you?") is different from authorization ("what can you do?"). Common mistakes:
- Not checking resource ownership (user A accessing user B's data)
- Missing authorization checks on admin routes
- Exposing internal IDs that enable enumeration attacks
Implement Row-Level Security in your database (PostgreSQL RLS) as a defense-in-depth layer on top of application-level authorization.
Rate Limiting
Every public API endpoint needs rate limiting. Standard approaches:
- IP-based rate limiting for unauthenticated endpoints
- User/API-key based rate limiting for authenticated endpoints
- Endpoint-specific limits (expensive operations get stricter limits)
Libraries: express-rate-limit (Node.js), slowapi (Python), or handle at the reverse proxy/gateway level (Nginx, Cloudflare).
Input Validation
Validate every input, every time. Never trust client data. In 2026, use Zod (TypeScript/JavaScript) or Pydantic (Python) for schema validation with clear error messages:
const createUserSchema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120),
username: z.string().min(3).max(50).regex(/^[a-zA-Z0-9_]+$/)
});
API Versioning
You will need to make breaking changes. Plan for this from day one:
- URL versioning:
/api/v1/users— most explicit, most common - Header versioning:
API-Version: 2— cleaner URLs but harder to test - Query parameter:
/api/users?version=2— simple but pollutes URLs
Support at least one previous major version simultaneously. Give 6–12 months deprecation notice with sunset headers.
API Documentation
Undocumented APIs create friction for every developer who uses them — including yourself 6 months later. Minimum documentation requirements:
- OpenAPI/Swagger specification for REST APIs (auto-generates interactive docs)
- Authentication guide with working examples
- Rate limit specifications
- Error code reference
- Changelog for breaking changes
Tools: Swagger UI, Redoc, Scalar (best-looking in 2026) for rendering OpenAPI specs.
API Observability
Production APIs need comprehensive monitoring:
- Metrics: Request rate, error rate, latency percentiles (P50, P95, P99) per endpoint
- Alerts: Error rate spike, latency degradation, unusual traffic patterns
- Logging: Structured logs with request ID, user ID, duration for every request
- Tracing: Distributed tracing (OpenTelemetry) for microservices to track request paths
A good observability setup means you know about API problems before your users do.
Building or improving your API infrastructure? Our backend engineering team specializes in production-grade API development. Let's discuss your requirements.