A well-designed API can feel like a developer's favorite kind of documentation. It shows what's available and how calls behave through a predictable, self-consistent interface. A poorly designed one creates support tickets and migration work nobody planned for.
API design is the process of planning and structuring an API's endpoints, request/response formats, authentication, versioning, and error handling before implementation begins. Design decisions are expensive to change after launch, as Joshua Bloch put it in his Google-published guidance on Public API permanence. External consumers depend on the contract you published, and every breaking change you ship becomes their migration project.
- Good API design starts with resource modeling and consistent naming conventions before writing any code
- REST and GraphQL serve different use cases; Strapi supports both within the same architecture
- Treat security and versioning as design concerns from the start, including authentication, authorization, and input validation
- Strapi 5 generates REST and GraphQL APIs from your content model, so many API design conventions are built into the workflow
Why API design matters
You feel API design quality quickly in day-to-day development. In a Carnegie Mellon University (CMU) API study, API users were 2.4 to 11.2 times faster when a method was located on the class they expected. A Microsoft developer study found that documentation and learning resources are among the most severe obstacles to learning new APIs. Amazon Web Services (AWS) Well-Architected describes API contracts as documented agreements between API producers and consumers.
A predictable, consistent interface reduces both usability and contract problems: once you learn one endpoint, you can usually guess how the rest behave.
APIs also outlive the applications that consume them. A contract that's easy to extend survives technology churn on both sides; a brittle one forces painful migrations.
And increasingly, the API is the product. Developer-centered API design puts API consumers in developers' shoes. Developer-first go-to-market depends on great documentation and easy sign-up, and treating APIs as digital products with owners and roadmaps works better than treating them as interfaces into IT systems. With this API-first approach, API decisions come before implementation begins.
REST vs GraphQL vs gRPC: choosing an API style
The history of API styles helps explain why REST, GraphQL, and gRPC occupy different sweet spots.
REST API design
REST API guide is resource-oriented: each resource gets an endpoint, and standard Hypertext Transfer Protocol (HTTP) methods (GET, POST, PUT, PATCH, DELETE) operate on it. Communication is stateless, and responses cache natively through HTTP mechanisms like Cache-Control and ETag per Request for Comments (RFC) 9111](https://www.ietf.org/rfc/rfc9111.html). REST can over-fetch or under-fetch data, especially across complex data relationships, which can force multiple round-trips. REST fits straightforward CRUD and public APIs, especially in systems where HTTP caching matters.
GraphQL API design
GraphQL query language has a strongly typed schema and, typically, a single endpoint. Per the GraphQL specification, an operation selects the set of information it needs and receives exactly that information and nothing more, which avoids over-fetching and under-fetching data.
Clients define the response shape, which makes GraphQL a strong fit for single-page and mobile apps with varied data needs, especially rapidly evolving frontends. GraphQL requires more involved server-side implementation, and caching needs additional tooling because of GraphQL caching limits. For a Strapi-specific breakdown, see REST or GraphQL in Strapi 5: when to pick each.
gRPC
gRPC is a Remote Procedure Call (RPC) framework that uses Protocol Buffers for binary serialization and requires HTTP/2 for transport, with support for unary, server, client, and bidirectional streaming per gRPC core concepts. It is built for low-latency, high-throughput communication, but Microsoft Learn notes that "it's impossible to directly call a gRPC service from a browser today" without a workaround like gRPC-Web.
For most readers of this guide, gRPC is worth knowing about for internal microservice communication in polyglot, low-latency systems; web-facing APIs usually use REST or GraphQL.
Quick comparison
| Dimension | REST | GraphQL | gRPC |
|---|---|---|---|
| Payload format | JavaScript Object Notation (JSON) or Extensible Markup Language (XML) | JSON (typically) | Protobuf (binary) |
| Transport | HTTP (any version) | HTTP (typically) | HTTP/2 (required) |
| Endpoint model | Multiple resource-based endpoints | Single endpoint (/graphql) | RPC service/method definitions |
| Caching | Native HTTP (Cache-Control, ETag) | Limited; needs persisted queries or client cache | Not based on HTTP caching |
| Best for | Public APIs, CRUD, cacheable content | Single-page apps, mobile, flexible field selection | Internal microservices, streaming |
API design best practices
Resource naming and endpoint structure
Model endpoints around resource nouns. The Microsoft Azure Architecture Center advises basing resource Uniform Resource Identifiers (URIs) on the resource represented in the path, with operations handled by HTTP methods: /api/books for a collection rather than verb-based paths such as /api/create-book. Plurals work well for collections (/api/books), with identifiers (IDs) for specific items (/api/books/{id}). For path segments, lowercase with hyphens keeps names predictable.
Keeping nesting to two levels usually saves trouble later. Microsoft warns that paths like /customers/1/orders/99/products are "difficult to maintain" and inflexible if relationships change. Understanding API endpoints helps clarify when to split nested relationships into separate requests. /api/authors/{id}/books is fine; anything deeper is often easier to maintain as separate requests.
HTTP methods and status codes
HTTP methods work best when they map cleanly to operations per RFC 9110: GET retrieves, POST creates, PUT replaces, DELETE removes; PATCH partially updates per RFC 5789. HTTP method semantics are precise: GET is safe and idempotent; PUT and DELETE are idempotent; POST is neither; PATCH is neither, so repeated calls can have compounding effects.
Use 200 (success), 201 (created), 204 (no content), 400 (bad request), 401 (unauthenticated), 403 (forbidden), 404 (not found), 409 (conflict), 429 (rate limited, per RFC 6585), and 500 (server error) as the baseline status-code vocabulary. Use status codes consistently across endpoints; Microsoft's Azure guidelines classify changing a status code as a breaking change. The API endpoint guide covers the request/response mechanics in more detail.
Pagination, filtering, and sorting
You'll save yourself a breaking change later if pagination is part of the first version. Google's API Improvement Proposal (AIP) 158 is explicit: methods returning collections "must provide pagination at the outset, as it is a backwards-incompatible change to add pagination to an existing method."
Offset-based pagination (?page=2&pageSize=10) is simple and supports jumping to arbitrary pages, but slows as offsets grow; the PostgreSQL documentation notes that "the rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient." Cursor-based pagination seeks directly via an indexed key. Cost stays constant regardless of depth, but access is sequential only.
For filtering guidance and sorting guidance, consistent query parameter conventions keep client code easier to reason about: ?sort=createdAt:desc, ?filters[status][$eq]=published. Strapi 5 implements these conventions out of the box, with pagination[page] and pagination[pageSize] (default 25, max 100), sort, filters, fields, and populate parameters documented in the REST API docs. The guide to Strapi's populate and filtering shows these in practice.
Modeling relationships
One-to-many relationships are usually clearest as nested resources: /api/authors/{id}/books signals the relationship between authors and their books. In GraphQL, GraphQL type system relationships live directly in the type system:
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Book {
id: ID!
title: String!
author: Author!
}Deep relationships can create performance problems. Lazy-load large related datasets rather than relying on unbounded populate queries, since Strapi does not populate relations by default. Strapi handles one-to-one, one-to-many, and many-to-many relations in its schema definitions, covered in Strapi Relations 101.
GraphQL schema design best practices
GraphQL schema design shifts the same API design work into types, fields, relationships, and query boundaries.
Design your schema around the business domain, using names from everyday business language rather than mirroring your database tables, per graphql.org's schema design guidance. Fields are nullable by default, and that's usually what you want: graphql.org recommends making fields non-null only when null is not an appropriate failure value, because an error on a non-null field nulls out the nearest nullable parent and discards otherwise-valid partial data.
For lists, connection types following the Relay Cursor Connections Specification give you a familiar structure: connections expose edges and pageInfo fields with hasNextPage and cursor markers. GraphQL.org recommends cursor-based pagination here too, calling it "the most powerful" option. For a full walkthrough of GraphQL in Strapi, see the Strapi GraphQL guide.
Performance: batching and caching
In GraphQL, the N+1 problem (one query for a list, then one query per item for related data) is a classic performance trap. Batching where appropriate and pagination on list fields help keep it under control. For caching, persisted documents let clients send known operations as GET requests that browsers and Content Delivery Networks (CDNs) can cache.
Abuse prevention belongs in the schema design phase. The Open Worldwide Application Security Project (OWASP) GraphQL Cheat Sheet warns that deeply nested queries function like recursive calls and cause denial of service; depth limits, query complexity analysis, and pagination on all list fields reduce that risk.
Common API design mistakes
Inconsistent field names and API naming conventions that mix verbs and nouns across endpoints break client code generation and force developers to memorize exceptions. Google standardizes on snake_case fields, Microsoft Graph on camelCase names; the specific convention matters less than applying one consistently across the entire API surface.
Excessive response fields and collections with no pagination waste bandwidth and degrade latency. Field selection helps here: Google AIP-157 field masks, Microsoft's $select, and Strapi's fields parameter all give consumers a way to ask for less. Because retrofitting pagination is a breaking change, it can't wait.
A bare {"error": "Something went wrong"} gives clients nothing to act on. RFC 9457 defines the application/problem+json format with type, title, status, detail, and instance members, plus extension fields for problem-specific data. Adopting it gives clients machine-readable failure modes instead of strings to parse.
Removing or renaming a field silently breaks running client workloads. Microsoft's Azure guidelines set the standard: "Already-running customer workloads must never break due to a service change." Additive, compatible changes are easier on consumers; when a breaking change is unavoidable, a new version with a documented deprecation timeline keeps the work visible.
Security and versioning
Security and versioning sit close together because both shape the public contract your consumers build against.
Authentication and authorization
Credential design usually separates delegated authorization and signed claims from quota tracking. OAuth 2.0 handles delegated authorization; RFC 9700, the security Best Current Practice published in January 2025, requires Proof Key for Code Exchange (PKCE) for public clients and advises against the implicit grant. JSON Web Token (JWT) carries signed claims between parties; RFC 8725 requires that "the entire JWT MUST be rejected" if any cryptographic operation fails to validate, and forbids accepting the none algorithm by default.
API keys identify clients for quota and usage tracking. AWS's guidance is direct: "Don't use API keys for authentication or authorization to control access to your APIs." Google Cloud authentication guidance and Azure API Management guidance give the same advice.
Separate identity and delegated authorization from quota tracking instead of using one credential type as a shortcut for all three.
The principle of least privilege is the safer default: grant each client only the permissions it needs, with granular read and write scopes rather than full access. Strapi 5 provides JWT-based authentication and role-based access control through its Users & Permissions system, and all Content-Types are private by default until you grant permissions. The authentication and authorization guide and the REST API authentication guide walk through the setup.
Input validation and rate limiting
Do not delegate validation to the client. OWASP validation guidance states that "input validation must be implemented on the server-side before any data is processed, as JavaScript-based validation on the client-side can be circumvented," and recommends allowlisting over blocklisting. Strapi 5 validates REST API input in controllers by default.
Rate limiting protects against OWASP's API4:2023, Unrestricted Resource Consumption. OWASP recommends a token-bucket or sliding-window algorithm over fixed-window counters, which allow bursts at window boundaries. The rate limiting in Strapi guide covers implementation options.
Use Transport Layer Security (TLS) everywhere, ideally TLS 1.3 per OWASP's TLS guidance, and keep production error messages generic enough that they don't leak implementation details.
Versioning your API
Pick one versioning mechanism and apply it consistently:
- URI versioning (
/api/v1/books) puts the version in the path; Google Cloud mandates it for its own APIs, and a 2024 API study found it in 26.23% of APIs surveyed. It was the most common option and the most visible to consumers. - Query parameter versioning (
?api-version=2024-01-01) is what Azure's guidelines require exclusively. - Header versioning (
Accept-version: v1) keeps endpoints clean but complicates caching; the same study found only 0.17% of APIs use it.
Choose one mechanism deliberately, document it, and apply it consistently across the API surface.
Whichever mechanism you choose, semantic versioning helps communicate the nature of changes: semantic versioning increments MAJOR for incompatible changes, MINOR for backward-compatible additions, and PATCH for bug fixes. The MAJOR number is what URI versioning exposes; minor and patch changes stay within the same URI version.
Strapi doesn't require manual versioning because its auto-generated APIs evolve with your content model. But if you build custom endpoints, versioning them becomes your responsibility from the first release.
API design in Strapi 5
Strapi 5 uses these design patterns to turn content models into API routes. For each Content-Type you define, it generates REST endpoints following the plural-noun convention: GET /api/:pluralApiId lists documents, POST /api/:pluralApiId creates one, and GET, PUT, and DELETE on /api/:pluralApiId/:documentId handle individual documents.
The optional GraphQL plugin adds a single /graphql endpoint with auto-generated queries and mutations per Content-Type. Strapi 5's response format is flattened compared to v4, with attributes directly on the data object and a stable 24-character documentId identifying each document across locales and draft/published states.
The Content-Type Builder handles resource modeling visually; a solid content model translates directly into a well-structured API surface. Role-based permissions apply per endpoint, so you control exactly which roles can read, write, or administer each resource. And when the defaults aren't enough, custom controllers and custom policies let you extend the API with your own logic while keeping the generated structure intact.
Designing APIs that last: key takeaways
Good API design improves developer experience and integration speed; stable contracts also help systems last longer. Start with clear resource modeling and consistent conventions, layer on security and versioning from day one, and document everything, because the contract you publish is the one you'll maintain for years. In Strapi 5, you define Content-Types in the Content-Type Builder, and Strapi generates REST and GraphQL APIs with authentication and granular permissions built in. If you want managed hosting for a Strapi project, Strapi Cloud is an option.






