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.

21CTO
21CTO
21CTO
Designing Practical RESTful APIs: Best Practices for Real‑World Apps

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendSecurityHTTPpaginationapi-designVersioningRESTful API
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.