Hello everyone,
As software developers, we can’t avoid APIs. Regardless of whether we build microservices ourselves or integrate external services – well-designed interfaces are the be-all and end-all for successful, maintainable systems. We have already talked about the Fundamentals of REST and examined the principles according to Roy Fielding. Today we’ll dive deeper and look at how to put these principles into practice. Let’s take a look at the GitHub REST API - it’s a great example of how to design scalable and intuitive interfaces.
Why good API design is so crucial
An API is basically a contract. A contract between your service and the clients who use it. If that contract is unclear, inconsistent, or difficult to use, no one will love your API, no matter how good the code behind it is. Poor design leads to:
- High integration effort: Clients have to invest a lot of time to understand your API and use it correctly.
- Susceptibility to errors: Misunderstandings in usage lead to bugs.
- Maintenance Nightmare: Any small change can cause breaking changes and make life difficult for clients.
- Low Adaptation: If it’s too complicated, your API simply won’t be used.
Good API design, on the other hand, leads to happy developers, smooth integrations and a future-proof architecture.
The pillars of RESTful API design
Let’s remember the core principles of REST that we have already discussed in detail: client-server separation, statelessness, cacheability, uniform interface, layered architecture and optionally code-on-demand. The “Uniform Interface” is particularly important in the design.
Let’s take a look at how the GitHub REST API implements these principles and what best practices can be derived from them.
1. Resource-centric URIs
The Uniform Interface begins with the addressability of resources via URIs. This means: Your URLs should be nouns, not verbs! They represent the entities (resources) that your client can interact with.
GitHub Example:
- False (verbs):
/getUsers,/createNewRepo - Correct (nouns, plural):
/users,/repos
GitHub uses clear, hierarchical URIs to represent relationships. For example, to get all issues from a specific repository:
GET /repos/{owner}/{repo}/issues
Here it is immediately apparent that issues is a subresource of repos, which in turn is associated with a specific {owner} and {repo}. But don’t keep the hierarchy too deep to maintain readability.
Best Practice:
- Use plural nouns for resource collections (e.g.
/users,/products). - Use hierarchies in URIs to represent relationships between resources (e.g.
/users/{userId}/posts). - Avoid verbs in URIs; the operation is defined by the HTTP method.
2. Use HTTP methods for actions (standard verbs)
The HTTP methods (GET, POST, PUT, PATCH, DELETE) are the “verbs” of your API. They define the action that is applied to a resource.
GitHub Example:
GET /users/{username}: Gets the details of a user.POST /repos/{owner}/{repo}/issues: Creates a new issue in a repository.PATCH /repos/{owner}/{repo}: Updates partial information of a repository.DELETE /repos/{owner}/{repo}: Deletes a repository.
GitHub uses these methods consistently and intuitively.
Best Practice:
GET: Get data (idempotent, secure).POST: Create new resources.PUT: Completely update/replace a resource.PATCH: Partially update a resource.DELETE: Delete a resource.
3. Meaningful HTTP status codes
Clients need to know whether their request was successful or what went wrong. HTTP status codes are the standard way to do this.
GitHub Example:
200 OK: Default success.201 Created: Resource created successfully (e.g. after aPOST).204 No Content: Request successful, but no content returned (e.g. afterDELETE).400 Bad Request: Client error, e.g. invalid input.401 Unauthorized: Authentication failed.403 Forbidden: Authenticated but not authorized.404 Not Found: Resource not found.422 Unprocessable Entity: Validation error on request.429 Too Many Requests: Rate limit exceeded.500 Internal Server Error: Server-side error.
Best Practice:
- Use the standard HTTP status codes as precisely as possible.
- For errors (4xx, 5xx), provide a consistent error structure in the response body that provides more details (e.g. error code, message).
4. Authentication and Authorization
REST itself does not define any security mechanisms, but relies on underlying protocols and established standards.
GitHub Example:
The GitHub API relies on Personal Access Tokens (PATs) and OAuth tokens for authentication. These tokens are usually sent in the Authorization header.
Authorization: token YOUR_PERSONAL_ACCESS_TOKEN
Best Practice:
- Use HTTPS for all communication to encrypt data.
- Use token-based authentication (e.g. OAuth 2.0, JWT, PATs).
- Implement Rate Limiting (like GitHub with the
429 Too Many Requestsstatus code). - Specify permissions (scopes) in fine granularity and only request the minimum permissions.
5. API versioning
APIs evolve, but clients expect stability. Versioning is the way to introduce breaking changes cleanly without breaking existing integrations.
GitHub Example:
GitHub uses date-based versioning in the X-GitHub-Api-Version header.
X-GitHub-Api-Version: 2022-11-28
Additive (non-breaking) changes are available in all supported API versions, while breaking changes require a new API version.
Best Practice:
- Prefix in URL:
api.example.com/v1/users(common) - Custom Header:
X-Api-Version: 1(like GitHub does, or Accept header) - Set a deprecation schedule: Announce old versions in a timely manner and set an end date for their support.
6. Data formats and return structures
RESTful APIs should use standardized and easy-to-process data formats.
GitHub Example:
GitHub prefers JSON as the payload format and sets the Content-Type header to application/json accordingly. For consistent responses, especially for errors or metadata (such as pagination), an envelope structure is often used.
Best Practice:
- Use JSON as the primary data format.
- Set the
Content-Typeheader correctly (e.g.application/json). - Ensure consistent answer structures, even in the event of errors. A common pattern is a
datafield for the actual resource and separate fields forerrorsormeta.
Conclusion
Designing a robust and maintainable REST API is an art, but not magic. By sticking to established principles, learning from best practices like the GitHub REST API, and staying consistent, we can create interfaces that not only work, but are fun to use. It’s about making the “contract” with our clients as clear and user-friendly as possible.
Which design principles are most important to you when developing APIs? Let me know!
Quellen:
- [1] Julian Paul - REST: Die Architektur hinter verteilten Systemen, https://julianpaul.dev/blogs/rest/
- [2] restful-api-guidelines/chapters/design-principles.adoc at main - GitHub, https://github.com/zalando/restful-api-guidelines/blob/master/chapters/design-principles.adoc
- [3] API Best Practices & Naming Conventions - GitHub, https://github.com/saifaustcse/api-best-practices
- [4] Getting Started With GitHub REST API - GeeksforGeeks, https://www.geeksforGeeks.org/git/getting-started-with-github-rest-api/
- [5] Authenticating to the REST API - GitHub Docs, https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api
- [6] Managing your personal access tokens - GitHub Docs, https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
- [7] API Versions - GitHub Docs, https://docs.github.com/rest/overview/api-versions
![[EN] RESTful API Design: Lerne von GitHub und bau' deine Schnittstellen rock-solid!](/images/REST-API-Design_BlogHeader.png)