Designing Practical RESTful APIs: Best Practices for Real‑World Apps
This article presents practical guidelines for designing, versioning, securing, and documenting RESTful APIs, covering resource modeling, URL conventions, filtering, sorting, pagination, authentication, rate limiting, error handling, and response formats to help developers build flexible and user‑friendly public APIs.
Background
There are countless articles on how to design RESTful APIs, but no universal standard exists for authentication, formatting, or versioning. When you start building an app, especially after the backend model is complete, you must carefully design the public API because once released it is hard to change.
This article shares a pragmatic approach to creating easy‑to‑use, deployable, and flexible APIs.
Basic API Design Requirements
While many academic viewpoints exist, this guide focuses on practical best practices. The following principles should be respected:
Follow standards when they make sense.
Make the API programmer‑friendly and easy to type in a browser.
Keep the API simple, intuitive, and elegant.
Provide enough flexibility to support the UI layer.
Balance the above principles when making trade‑offs.
Remember: an API is the programmer’s UI, so its user experience must be carefully considered.
Using RESTful URLs and Actions
The widely accepted RESTful design principle, introduced by Roy Fielding, treats APIs as collections of logical resources manipulated via HTTP verbs (GET, POST, PUT, DELETE).
Defining Resources
Resources should be nouns from the API consumer’s perspective. Expose only the necessary external resources, not every internal model entity. In the example system, the resources are ticket, user, and group.
Once resources are defined, map allowed actions to HTTP methods:
GET /tickets – retrieve ticket list
GET /tickets/12 – retrieve a specific ticket
POST /tickets – create a new ticket
PUT /tickets/12 – update ticket 12
DELETE /tickets/12 – delete ticket 12
Using REST lets you leverage HTTP’s built‑in CRUD capabilities with a single endpoint ( /tickets).
Endpoint Naming (Singular vs. Plural)
Adopt a consistent plural form for collection endpoints (e.g., /tickets) to keep URLs tidy and predictable.
Handling Associations
For related resources, expose sub‑resource URLs:
GET /tickets/12/messages – list messages for ticket 12
GET /tickets/12/messages/5 – retrieve message 5
POST /tickets/12/messages – create a new message
PUT /tickets/12/messages/5 – update message 5
PATCH /tickets/12/messages/5 – partially update message 5
DELETE /tickets/12/messages/5 – delete message 5
If the associated resource is independent, return a link; if it is tightly coupled, embed the resource data directly.
Non‑CRUD Operations
When an action does not fit CRUD, consider:
Refactor the action into a resource (e.g., activated with PATCH).
Treat it as a sub‑resource (e.g., PUT /gists/:id/star to star a gist).
Use a clear custom endpoint such as /search and document it thoroughly.
Always Use SSL
Enforce HTTPS for all requests. SSL simplifies authentication (a single token) and protects against insecure access. Never redirect non‑SSL URLs to SSL URLs.
Documentation
Documentation should be public, easy to find, and contain request/response examples (cURL, clickable links). Keep it up‑to‑date, include deprecation schedules, and consider mailing lists or blogs for announcements.
Versioning
Include version information to avoid breaking existing clients. Versions can be placed in the URL (e.g., /v1/tickets) or in headers; URLs make cross‑version access straightforward.
Result Filtering, Sorting, and Search
Implement filtering, sorting, and search via query parameters:
Filtering: GET /tickets?state=open Sorting: GET /tickets?sort=-priority,created_at Search: GET /tickets?q=return&state=open&sort=-priority,created_at Provide aliases for common queries to improve readability.
Field Limiting
Allow clients to request only needed fields to reduce payload size, e.g., GET /tickets?fields=id,subject,updated_at&state=open.
Returning Resources on Write Operations
POST/PUT/PATCH should return the created or updated representation (e.g., 201 Created with a Location header pointing to the new resource).
HATEOAS Consideration
While HATEOAS can describe available actions in resource metadata, it is not yet universally adopted; use it only if it adds clear value.
JSON‑Only Responses
Prefer JSON over XML for simplicity and readability. If XML support is required, consider using URL suffixes ( .json, .xml) and negotiate via the Accept header.
Naming Conventions
Follow language‑specific conventions: camelCase for JavaScript/Java/C#, snake_case for Python/Ruby. Consistency across services is key.
Pretty‑Print and Gzip
Enable pretty‑print for human‑readable output by default, but allow disabling via a query parameter. Always support gzip compression to save bandwidth.
$ curl https://api.github.com/users/veesahni > with-whitespace.txt $ ruby -r json -e 'puts JSON.pretty_generate(JSON.parse(STDIN.read))' < with-whitespace.txt > without-whitespace.txt $ gzip -c with-whitespace.txt > with-whitespace.txt.gz $ gzip -c without-whitespace.txt > without-whitespace.txt.gz
Whitespace adds ~8.5% overhead without gzip; gzip reduces overall size dramatically.
Envelope Wrapper
Wrap responses in an envelope only when necessary (e.g., JSONP or when clients cannot read HTTP headers). Otherwise, rely on standard headers for pagination and metadata.
Input Formats
Accept JSON for complex inputs; URL‑encoded forms are simple but lack type information and hierarchy.
Pagination
Prefer HTTP Link headers for pagination (RFC 5988) rather than embedding pagination data in the response body.
Embedding Related Resources
Allow an embed query parameter to include related resources inline, but beware of N+1 query problems.
HTTP Method Override
Support X-HTTP-Method-Override header for clients that can only send GET or POST.
Rate Limiting
Implement rate limiting using HTTP 429 responses and include headers such as X-Rate-Limit-Limit, X-Rate-Limit-Remaining, and X-Rate-Limit-Reset (seconds remaining).
Authentication
APIs should be stateless; each request must carry authentication credentials (token or OAuth 2). For JSONP, tokens may be passed as query parameters, but be aware of logging risks.
Caching
Use ETag and Last‑Modified headers to enable client‑side caching. Return 304 Not Modified when appropriate.
Error Handling
Return JSON error objects containing a code, message, and optional description. Validate input for PUT/POST/PATCH and return structured error details.
{ "code": 1234, "message": "Something bad happened :-(", "description": "More details about the error here" }
HTTP Status Codes
Use standard status codes: 200 OK, 201 Created, 304 Not Modified, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed, 410 Gone, 415 Unsupported Media Type, 422 Unprocessable Entity, 429 Too Many Requests.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
