Skip to main content

API Design

RESTful API and interface design best practices

17
Best Practices
8
FAQs
1
Benchmarks

Best Practices

Google API Design Guide

Opinionated REST and gRPC design rules: resource-oriented URIs, plural nouns, pagination, errors.

by Google

Microsoft REST API Guidelines

Cross-company REST consistency rules (nouns, verbs, versioning, errors).

by Microsoft

Stripe API Versioning Policy

Backwards-compatible evolution strategy and pinned versions for API consumers.

by Stripe

Backends for Frontends (BFF)

An architecture pattern that gives each frontend client its own tailored backend service, instead of forcing web, mobile, and other clients to share one general-purpose API.

by Sam Newman

API Gateway Pattern

An architecture pattern that places a single entry point in front of backend services to handle routing, authentication, rate limiting, and other cross-cutting concerns.

by Microsoft

Richardson Maturity Model

A four-level model for grading how fully an HTTP API embraces REST, from RPC-style endpoints up to hypermedia controls (HATEOAS).

by Leonard Richardson

OpenAPI Specification Best Practices

Guidance for writing accurate, machine-readable OpenAPI documents that describe HTTP APIs and drive docs, client SDKs, mocks, and contract tests.

by OpenAPI Initiative (Linux Foundation)

GraphQL API Best Practices

Practical guidance for designing GraphQL schemas and servers: typed schemas, pagination, error handling, query cost limits, and avoiding the N+1 problem.

by GraphQL Foundation

gRPC Best Practices

Guidance for building high-performance gRPC services with Protocol Buffers: service design, streaming, deadlines, error codes, and backward-compatible schema evolution.

by Cloud Native Computing Foundation

API-First Design

An approach that treats the API contract as a product designed before implementation, so teams agree on the interface, then build clients and servers in parallel.

by OpenAPI Initiative (Linux Foundation)

Idempotency Keys

A pattern where clients send a unique key with unsafe requests so the server can safely retry without applying the same operation twice, preventing duplicate charges or records.

by Stripe

API Rate Limiting

Controlling how many requests a client can make in a time window to protect API capacity, ensure fair use, and defend against abuse, using algorithms like token bucket.

by IETF

API Pagination Best Practices

Techniques for returning large result sets in pages without breaking under concurrent writes: offset, cursor (keyset), and page-token pagination, with stable ordering.

by Google

Webhook Best Practices

Guidance for sending and receiving reliable webhooks: signature verification, idempotent handlers, retries with backoff, and fast acknowledgement of events.

by Stripe

Problem Details for HTTP APIs (RFC 9457)

An IETF standard JSON format for machine-readable HTTP error responses, defining fields like type, title, status, detail, and instance for consistent error handling.

by IETF

API Backward Compatibility

Evolving an API without breaking existing clients by making only additive changes, versioning breaking changes, and deprecating fields gracefully over time.

by Google

JSON:API Specification

A convention for building JSON APIs that standardizes resource structure, relationships, pagination, filtering, and sparse fieldsets to reduce bikeshedding and over-fetching.

by JSON:API

Patterns

Anti-Corruption Layer

Create a translation layer between new and legacy systems to prevent legacy concepts from leaking into new code

Backend for Frontend (BFF)

Create separate backend services tailored to each frontend's needs

Gateway Aggregation

Uses a gateway to combine multiple backend requests into one, so clients make a single call instead of many.

Gateway Offloading

Moves shared cross-cutting functionality such as TLS, auth, and rate limiting out of services and into a gateway.

Gateway Routing

Routes client requests to the correct backend service through a single endpoint using request attributes such as path or host.

Message Translator

Converts a message from one data format or schema to another so systems with incompatible representations can communicate.

Request-Reply

Lets a requestor send a message and receive a corresponding response over messaging, combining two one-way channels into a logical two-way exchange.

Idempotency Key

Attaches a unique key to a request so the server can detect and de-duplicate retries, making non-idempotent operations safe to repeat.

Pagination

Splits a large result set into smaller pages so APIs and UIs can return and traverse data incrementally instead of loading everything at once.

HATEOAS

Hypermedia as the Engine of Application State: REST responses include links describing available actions, letting clients navigate the API by following links.

Idempotency Key

A client-supplied unique key lets a server detect and dedupe retried requests, so repeated submissions produce the same result exactly once.

API Versioning

Strategies for evolving an API without breaking existing clients, by exposing multiple versions through URLs, headers, or media types.

Tutorials

Building a GraphQL API from REST Endpoints

Transform your REST API into a GraphQL server with Apollo

How to Add Rate Limiting to an API

Protect an API with token-bucket rate limiting, return standard rate-limit headers, and share limits across instances with Redis.

How to Build a REST API with Best Practices

Design resource-oriented routes, use correct status codes, validate input, handle errors consistently, and version your REST API.

How to Build a GraphQL Server

Define a schema, write resolvers, solve the N+1 problem with batching, and add error handling and depth limits to a GraphQL API.

How to Build a gRPC Service

Define a service in Protocol Buffers, generate code, implement unary and streaming methods, and call the service from a client.

How to Implement Secure Webhooks

Send and receive webhooks with HMAC signatures, idempotency keys, retries with backoff, and replay protection.

How to Version an API Without Breaking Clients

Choose a versioning scheme, evolve schemas with additive changes, deprecate old versions gracefully, and communicate changes to clients.

How to Add Cursor-Based Pagination to a REST API

Replace offset pagination with stable cursor pagination, return navigation links, and keep results consistent under concurrent writes.

Checklists

API-First Design Review Checklist

Review an API design before implementation to ensure contract, versioning, and consistency standards are met up front.

Backward-Compatibility Review Checklist

Review a change to a service, API, or schema to confirm existing consumers and data continue to work without breaking.

API Design Review Checklist

Review items for evaluating a new or changed HTTP API against design, consistency, and developer-experience standards.

API Versioning and Deprecation Checklist

Verification items for introducing a new API version and retiring an old one without breaking existing consumers.

GraphQL Migration Readiness Checklist

Readiness items for migrating a REST API or adding a GraphQL layer without losing performance, security, or observability.

Webhook Reliability Checklist

Verification items for delivering and consuming webhooks reliably, covering signing, retries, idempotency, and ordering.

Third-Party Integration Cutover Checklist

Cutover items for switching to or replacing a third-party API or vendor integration with minimal disruption.

API Rate Limiting and Throttling Readiness Checklist

Verification items for designing fair, abuse-resistant rate limiting and throttling for a public or internal API.

FAQs

What is the difference between REST and GraphQL?

REST exposes data through multiple fixed endpoints, each returning a predefined resource representation, and relies on HTTP verbs and status codes. GraphQL exposes a single endpoint and lets the client specify exactly which fields it wants in one query, avoiding over-fetching and under-fetching. REST is simpler to cache with standard HTTP tooling, while GraphQL offers flexible queries at the cost of more complex caching and server-side query-cost controls. Choose REST for simple, resource-oriented APIs and GraphQL when clients need varied, nested data with fewer round trips.

What is gRPC and when should I use it?

gRPC is a high-performance remote procedure call framework that uses Protocol Buffers for compact binary serialization and HTTP/2 for transport, including multiplexed streams. It supports unary calls plus client, server, and bidirectional streaming, and generates strongly typed client and server stubs from a `.proto` contract. gRPC excels for low-latency, high-throughput service-to-service communication inside a network. It is less suited to direct browser use, where REST or GraphQL over JSON is usually easier, though gRPC-Web bridges some of that gap.

What is an API gateway?

An API gateway is a server that sits in front of one or more backend services and acts as a single entry point for clients. It handles cross-cutting concerns such as routing, authentication, rate limiting, request and response transformation, caching, and observability, so individual services do not each reimplement them. In microservice architectures it also aggregates calls and shields clients from internal topology changes. Common implementations include managed offerings and self-hosted proxies like Kong, Envoy, and NGINX.

What is rate limiting and how does it work?

Rate limiting caps how many requests a client may make in a given window to protect a service from overload, abuse, and runaway costs. Common algorithms include the token bucket and leaky bucket, which allow bursts up to a limit, and fixed or sliding windows that count requests per interval. When a client exceeds the limit the API typically returns HTTP 429 Too Many Requests, often with a `Retry-After` header. Limits are usually keyed by API key, user, or IP, and well-behaved clients should back off and retry rather than hammering the endpoint.

What does idempotency mean in APIs?

An operation is idempotent if performing it multiple times has the same effect as performing it once. In HTTP, GET, PUT, and DELETE are defined as idempotent while POST generally is not, which matters when a network error makes a client unsure whether its request succeeded. Designing idempotent endpoints lets clients safely retry without creating duplicate side effects such as double charges or duplicate records. For inherently non-idempotent operations, an idempotency key lets the server deduplicate retries.

What is an idempotency key?

An idempotency key is a unique client-generated identifier, often a UUID, sent with a request so the server can recognize and safely deduplicate retries of the same operation. On the first request the server processes it and stores the result keyed by that value; if a retry arrives with the same key, the server returns the original result instead of repeating the side effect. This is essential for non-idempotent operations like payments or order creation where network failures may prompt automatic retries. Keys are usually passed in a header such as `Idempotency-Key` and expire after a defined window.

What is a webhook?

A webhook is a way for one system to push real-time notifications to another by sending an HTTP POST to a URL the receiver registered in advance. Instead of the receiver repeatedly polling for changes, the source service calls back when an event occurs, such as a payment succeeding or a build finishing. Receivers should verify authenticity, commonly via an HMAC signature header, respond quickly with a 2xx, and process work asynchronously. Because delivery can fail or duplicate, robust webhook handlers are idempotent and rely on the sender's retry mechanism.

What are common API versioning strategies?

API versioning lets you evolve an interface without breaking existing clients. Common approaches are URI versioning (`/v1/orders`), which is explicit and cache-friendly; header or media-type versioning, which keeps URLs stable but is less visible; and query-parameter versioning, which is simple but easy to overlook. A strong practice is to version only on breaking changes, add new fields additively, and document deprecation timelines clearly. Whichever scheme you pick, apply it consistently and give clients a clear migration path before retiring an old version.

Benchmarks

API Translation Benchmark

Measures ability to translate between API specifications (REST, GraphQL, gRPC)

Vibgrate CLI

See a real scan run

A replay of the actual CLI running against our test repositories — live progress, real findings, a genuine DriftScore. Nothing executes in your browser.

Replay
demo@vibgrate — bash
npx @vibgrate/cli scan
 
╭──────────────────────────────────────────╮
Vibgrate Drift Report
╰──────────────────────────────────────────╯
 
── node-turborepo (node) .
Runtime: >=18.0.0 (6 majors behind)
Frameworks:
Turbo: 1.13.4 → 2.10.8 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 1 1-behind 3 2+ behind 1 unknown
 
── @repo/admin (node) apps/admin
Frameworks:
TanStack Query: 5.101.4 → 5.101.4 (current)
React: 18.3.1 → 19.2.8 (1 behind)
React DOM: 18.3.1 → 19.2.8 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vite: 5.4.21 → 8.2.1 (3 behind)
Dependencies:
3 current 9 1-behind 3 2+ behind 4 unknown
 
── @repo/api (node) apps/api
Frameworks:
Express: 4.22.2 → 5.2.1 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 4.1.10 (3 behind)
Dependencies:
7 current 5 1-behind 3 2+ behind 4 unknown
 
── @repo/web (node) apps/web
Frameworks:
Next.js: 14.2.35 → 16.3.0 (2 behind)
React: 18.3.1 → 19.2.8 (1 behind)
React DOM: 18.3.1 → 19.2.8 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 6 1-behind 3 2+ behind 5 unknown
 
── @repo/config (node) packages/config
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 2 1-behind 5 2+ behind 0 unknown
 
── @repo/database (node) packages/database
Frameworks:
Prisma: 5.22.0 → 7.9.1 (2 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 0 1-behind 3 2+ behind 1 unknown
 
── @repo/types (node) packages/types
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
0 current 0 1-behind 1 2+ behind 1 unknown
 
── @repo/ui (node) packages/ui
Frameworks:
React: 18.3.1 → 19.2.8 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
React: 18.3.1 → 19.2.8 (1 behind)
Dependencies:
1 current 4 1-behind 1 2+ behind 1 unknown
 
── @repo/utils (node) packages/utils
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 4.1.10 (3 behind)
Dependencies:
0 current 1 1-behind 2 2+ behind 1 unknown
 
Tech Stack
Frontend: React, React DOM
Meta-frameworks: Next.js
Bundlers: tsx, Turbo, Vite
CSS / UI: Autoprefixer, PostCSS, Tailwind CSS
Backend: Express
ORM / Database: Prisma, Prisma Client
Testing: Vitest
Lint & Format: ESLint, ESLint Prettier, ESLint React, Prettier, typescript-eslint
 
Services & Integrations
Auth: JWT 9.0.3
Databases: Prisma 5.22.0
 
TypeScript
v5.3.3 · strict ✔ · MIXED · target: ES2022
 
Build & Deploy
Package Managers: pnpm
Monorepo: npm-workspaces, pnpm-workspaces, turbo
 
Product Purpose Signals
Frameworks: react, nextjs
Evidence: 177
Top Signals:
- [heading] Dashboard (apps/admin/src/pages/Dashboard.tsx)
- [title] Revenue Overview (apps/admin/src/pages/Dashboard.tsx)
- [copy] workspace:* (packages/ui/package.json)
- [copy] ./dist (packages/ui/tsconfig.json)
- [copy] ./src/index.ts (packages/ui/package.json)
- [copy] @repo/config/tsconfig-base.json (packages/ui/tsconfig.json)
- [copy] @repo/ui (packages/ui/package.json)
- [copy] #3b82f6 (apps/admin/src/pages/Dashboard.tsx)
Unknowns:
- No pricing or billing evidence found.
- No integrations/connectors evidence found.
- No route structure evidence found.
 
Security Posture
Lockfile ✖ · .env ✔ · node_modules ✔
 
Platform
Native modules: turbo
 
Code Quality
Files: 36 · Functions: 183 · Avg complexity: 2.62 · Avg length: 21.13 lines
Max nesting: 2 · Circular deps: 0 · Dead code: 0%
God files: apps/admin/src/pages/Products (448 lines)
 
Database Schema
postgresql · 8 models · 1 enum
Models: Address, CartItem, Category, Order, OrderItem (+3 more)
 
Findings (16 errors, 11 warnings)
Node.js runtime ">=18.0.0" reached end-of-life on 2025-04-30 (latest: 24.0.0).
vibgrate/runtime-eol in .
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in .
60% of dependencies are 2+ major versions behind in node-turborepo.
vibgrate/dependency-rot in .
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.1.2).
vibgrate/dependency-major-lag in .
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/admin
Vite is 3 major versions behind (current: 5.4.21, latest: 8.2.1).
vibgrate/framework-major-lag in apps/admin
vite is 3 major versions behind (spec: ^5.0.12, latest: 8.2.1).
vibgrate/dependency-major-lag in apps/admin
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/api
Vitest is 3 major versions behind (current: 1.6.1, latest: 4.1.10).
vibgrate/framework-major-lag in apps/api
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.1.2).
vibgrate/dependency-major-lag in apps/api
vitest is 3 major versions behind (spec: ^1.2.1, latest: 4.1.10).
vibgrate/dependency-major-lag in apps/api
Next.js is 2 major versions behind (current: 14.2.35, latest: 16.3.0).
vibgrate/framework-major-lag in apps/web
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/web
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.1.2).
vibgrate/dependency-major-lag in apps/web
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/config
56% of dependencies are 2+ major versions behind in @repo/config.
vibgrate/dependency-rot in packages/config
eslint-plugin-react-hooks is 3 major versions behind (spec: ^4.6.0, latest: 7.1.1).
vibgrate/dependency-major-lag in packages/config
Prisma is 2 major versions behind (current: 5.22.0, latest: 7.9.1).
vibgrate/framework-major-lag in packages/database
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/database
75% of dependencies are 2+ major versions behind in @repo/database.
vibgrate/dependency-rot in packages/database
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/types
100% of dependencies are 2+ major versions behind in @repo/types.
vibgrate/dependency-rot in packages/types
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/ui
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/utils
Vitest is 3 major versions behind (current: 1.6.1, latest: 4.1.10).
vibgrate/framework-major-lag in packages/utils
67% of dependencies are 2+ major versions behind in @repo/utils.
vibgrate/dependency-rot in packages/utils
vitest is 3 major versions behind (spec: ^1.2.1, latest: 4.1.10).
vibgrate/dependency-major-lag in packages/utils
 
╭──────────────────────────────────────────╮
Top Priority Actions
╰──────────────────────────────────────────╯
 
1. Upgrade EOL runtime in node-turborepo
End-of-life runtimes no longer receive security patches and block ecosystem upgrades.
./.
>=18.0.0 → 24.0.0 (6 majors behind)
Impact: −10 drift points (runtime & EOL)
 
2. Fix security posture: no lockfile found
Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.
./
Missing: package-lock.json, pnpm-lock.yaml, or yarn.lock
 
3. Upgrade Vite 5.4.21 → 8.2.1 in @repo/admin (+2 more)
3 major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.
./apps/admin
Vite: 5.4.21 → 8.2.1 (3 majors behind)
./apps/api
Vitest: 1.6.1 → 4.1.10 (3 majors behind)
./packages/utils
Vitest: 1.6.1 → 4.1.10 (3 majors behind)
Impact: −5–15 drift points
 
4. Reduce dependency rot in @repo/types (100% severely outdated)
1 of 1 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/types
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
5. Reduce dependency rot in @repo/database (75% severely outdated)
3 of 4 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/database
@prisma/client: 5.22.0 → 7.9.1 (2 majors behind)
prisma: 5.22.0 → 7.9.1 (2 majors behind)
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
╭──────────────────────────────────────────╮
Architecture Layers
╰──────────────────────────────────────────╯
 
Archetype: monorepo (80% confidence)
Files classified: 29 (6 unclassified)
 
presentation 9 files drift ████████████████████ 100 risk high
routing 4 files drift ████████████████████ 100 risk high
middleware 2 files drift ███████▍░░░░░░░░░░░░ 37 risk moderate
domain 4 files drift ████████████████████ 100 risk high
data-access 2 files drift ████████████████████ 100 risk high
infrastructure 0 files drift ░░░░░░░░░░░░░░░░░░░░ 0 risk none
config 3 files drift ░░░░░░░░░░░░░░░░░░░░ 0 risk none
shared 5 files drift ████████████████████ 100 risk high
testing 0 files drift ████████████████████ 100 risk high
 
╭──────────────────────────────────────────╮
DriftScore Summary
╰──────────────────────────────────────────╯
 
DriftScore: 66/100
Risk Level: HIGH
Projects: 9
Classified: 8 nano · 1 micro · 0 small · 0 standard
Billable: 0.42 · 9 detected → 0.42 billable projects (micro-project pricing)
0.1 micro · 0.32 nano
These fractions add up across repositories, then round down to whole billable projects.
 
Score Breakdown
Runtime: ████████████████████ 100
Frameworks: █████████▏░░░░░░░░░░ 46
Dependencies: ██████░░░░░░░░░░░░░░ 30
EOL Risk: ████████████████████ 100
 
Scanned at 2026-08-07T06:14:10.284Z · 25.2s · 286 files scanned · 56 workspace files · 27 dirs
Press Run to start.