At MindK, we develop internal APIs for almost all of our projects.
Knowing how to create an API can give you a number of advantages like higher development speed, streamlined architecture (backend/frontend separation), better scalability with microservices, simpler integrations, easier testing, better security, easier compliance with GDPR and other regulations.
Now, let’s find out how to make an API.
Your API should bring value both to your organization and its intended users.
If you’re building a private API, those would be your colleagues. With public APIs, you’ll need more information about your target audience:
- Who are the developers that could benefit from your API (their domain, needs, goals, etc.)?
- How can you incorporate their needs into your API?
- How can you provide a better developer experience?
- What tools you need to provide along with your API (developer programs, SDKs, documentation, educational resources, etc.).
Understanding the needs of your users will help you define requirements and ensure a long-term success for your API.
Now, when you have a list of requirements (try to keep it short), it’s time to learn how to design an API.
Read more: API development business value.
Before you write the first line of code, you should come up with a design that fits your requirements and reflects the needs of developers that will consume your API.
Regardless of your architectural style, an API has to meet a number of requirements:
- Usability: developers should be able to learn and use your API with minimum effort;
- Reliability: the service should have minimal downtime;
- Scalability: the system should handle load spikes;
- Testability: testers should be able to easily identify any defects; and
- Security: the API should be protected from malicious users.
Here’s how you can satisfy these requirements.
1. Separate API design into several layers
These layers, each responsible for a single requirement, will sit between the client and the API logic.
Keep your API as small as possible. You can always add functionality but never remove it.
2. Choose your architectural style: REST vs SOAP
There are two common approaches to API architecture:
|Simple Object Access Protocol (SOAP)||Representational State Transfer (REST)|
|Provides data as services (i.e. verbs + nouns).||Provides data as representations of resources (i.e. nouns).|
|Uses a verbose XML data format for communication that consumes more bandwidth.||Uses a variety of data formats including JSON, XML, HTML, and plain text.|
|An official protocol with strict guidelines.||A flexible architectural style with a number of loose guidelines.|
|Works with application layer protocols like HTTP, UDP, and SMTP.||Works only with HTTP.|
|Requests can’t be cached.||Requests can be cached.|
|Requires detailed API contracts.||No detailed contracts needed.|
|Has built-in security, error handling, and authorization.||Developers have to take care of security, error handling, and authorization themselves.|
|Benefits: higher security and extensibility.||Benefits: higher performance, scalability, and flexibility.|
|Great for: legacy and enterprise apps with high security requirements (e.g. payment gateways, ERP systems, CRM software).||Great for: web and mobile apps.|
Currently, REST is the most popular approach to building web APIs. Its flexibility gives you more freedom to create an API as long as your architecture follows six constraints:
- Statelessness: as servers store no information about previous interactions, each call should provide the necessary context.
- Separation of concerns: the app’s backend should be developed independently from its UI.
- Caching of responses: servers should inform the clients whether the response is cacheable.
- Multiple communication layers between the server and the client.
- Uniform UI: calls from different clients look alike (i.e. one resource must have one URI).
- Code on request: if requested, API responses might include executable code.
As REST relies on a familiar HTTP protocol, developers can get up to speed much faster.
REST can use a human-readable JSON format. It’s more lightweight than XML, easier to parse, and works with all programming languages.
If your API needs to work with both JSON and XML (e.g. with legacy systems), you can change output depending on the requested format via request headers.
It’s recommended to follow the OpenAPI Specification when you create REST APIs. It’s a widely accepted and language-agnostic standard for building API interfaces. It allows both machines and humans to understand the API functionality without accessing source code or reading the documentation.
You can use the standard to generate documentation, clients, and servers, in different languages.
3. Think about security
APIs can be a major source of vulnerabilities (e.g. improper authentication, API keys in URI, unencrypted sensitive data, injections, replay attacks, stack trace leaks, DDoS attacks, etc.).
That’s why you should pay enough attention to security at the design stage. You have to make sure you’re not accidentally exposing sensitive information or leaving a backdoor to hackers.
Most APIs have 4 security layers:
You can use unique randomized IDs called API keys to identify developers accessing your API. They allow monitoring of users and detecting “unlawful” behavior.
As API keys aren’t encrypted, you’ll need other security measures to protect your API. Moreover, sending them in URI makes it possible to extract the keys from browser history.
It’s recommended to send the keys via the Authorization HTTP header as it isn’t recorded in the network logs.
Logins and passwords prove that developers accessing your API are those whom they claim to be.
You can use OpenID for authentication that redirects developers to an authorization server to confirm their identity.
Authenticated users need a list of permissions that match their access level.
It’s recommended to use SSL/TLS encryption to protect API traffic against credential hijacking and eavesdropping (even with private APIs).
Another requirement is using end-to-end encryption for sensitive data (e.g. medical records or payment details). You can also use tokenization or mask the data from appearing in logs and trace tools.
Security is often built into API frameworks. AdonisJs, for example, has JWT authentication out of the box. Another part of security comes from the way you deploy your API. We prefer AWS for its large scale security features.
After you’ve finished your API design, it’s time to start building! Here is a number of best practices that will help you create an API:
Build API endpoints
Endpoints are URLs that return different responses depending on the method used in the request.
With the REST architecture, your endpoints should provide data as nouns, not verbs+nouns (e.g. https://www.yoursite.com/users, not https://www.yoursite.com/get-user-data).
The verb part comes in the form of HTTP methods used in headers. They correspond to CRUD functions of a database.
- GET is used to READ resources from your database. As GET can’t modify data, it’s considered a safe method.
The method should either return a 200 OK code (if the resource is found), 404 Not Found (if the resource is missing) or 400 Bad Request (invalid syntax).
- POST is used to CREATE a new subordinate resource in your database.
If the resource was successfully created, the method should return a 201 OK code together with a location header and an entity describing the request status. If the method created a resource that cannot be identified via a URI, you can return either a generic 200 Ok code or 204 No Content.
When using a POST request to create a new resource, its effects must be restricted to the new resource.
- PUT is used to UPDATE the whole resource.
The method should either return a 201 OK code (if a new resource was created) or 200 OK/204 No Content when updating an existing resource.
- PATCH is used to UPDATE a part of a resource.
- DELETE is used to DELETE a resource.
The method should either return a 200 OK (if the resource was successfully deleted), 202 Accepted (if the action is in a queue), or 204 No Content (if the resource was deleted but the response is empty). Attempting to delete a resource for the second time should return a 404 Not Found.
GET, PUT, DELETE, HEAD, and PATCH requests must be idempotent (repeating the same call to the same resource must lead to the same state.)
For the sake of consistency, it’s recommended to always use plural for your resources and follow standard naming conventions.
Handle exceptions and errors
Your API should properly handle all exceptions and return correct HTTP status codes instead of a generic 500.
If the API can’t complete an action due to an exception, it’s recommended to describe the error that caused it in the response message.
Be careful as APIs can leak sensitive information via stack traces in error messages. They can show the names of servers, frameworks, classes, versions, and SQL queries used on the project.
This information can be used to exploit known vulnerabilities in the aforementioned resources.
So ensure your API returns a balanced error message without stack traces.
You can use an API gateway to standardize your error messages and avoid exposing sensitive information.
A leaky stack trace in error message; source: troyhunt.com
Sudden increases in traffic can disrupt your API (which is often used in Denial of Service attacks).
To prevent this, use traffic quotas (a limit on the number of requests an app can make per hour/week/month, etc.), spike arrests (a rate at which an app can make requests per minute/second + throttle the exceeding calls); or concurrent rate limits (an app can’t make more than x parallel connections to your API).
Use hyperlinks for navigation to related resources
Some of your resources will have relations to other resources. For example, yoursite.com/users refers to all users while yoursite.com/users/123 points to a specific user. When your API receives a GET request, it should provide links to all related resources in a response body. These links can also describe the supported request methods.
Implement pagination and search by criteria
Sometimes, API responses contain too much data. For example, there can be thousands of products relevant to a search in an e-commerce app. Sending all of that in a single response would be extremely taxing on your database.
To decrease response times and protect your API against DDoS attacks, you can split the data into several “pages”. For easier navigation, each page has its own URI. The API should only display a portion of data in one go and let users know how many pages are remaining.
There are several pagination methods to choose from:
- HTTP range headers (for binary data);
- Fixed data pages (all pages have an equal size);
- Flexible data pages (the client app specifies the page size);
- Offset and count (instead of dividing the data into pages, the API views it as a collection of items. The client can specify a starting index and the number of items to be returned); and
- Default values.
For easier sorting, use various filters like time of creation or price, and implement search via a query string.
Enable HTTP method overriding
As many firewalls don’t support PUT, PATCH, and DELETE methods, you should allow your API to override request methods with a custom X-HTTP-Method-Override header.
Implement client-side caching
Caching can save bandwidth and reduce latency as clients won’t have to load the recently fetched data.
After receiving a GET request, your API could send a Cache-Control header specifying whether the data in the response is cacheable. The header can also indicate when the data will be considered expired.
Optimize your API for performance
Requesting large binary resources like images can slow API responses. One way to avoid such issues is to enable the Accept-Ranges header for GET requests for large resources.
You can also use HTTP compression to cut the size of large objects.
By combining compression with streaming, you can reduce the latency even further. If a client requests large resources, you can partition the replies. The transmission ends when the client receives the last chunk of data that has a zero size.
If you can’t be sure about the amount of data in the response, it’s recommended to use a Transfer-Encoding: chunked header instead of a Content-Length header.
If your API detects that the data in a request is higher than the specified limit, it can deny the transmission and respond with the 413 (Request Entity Too Large) code.
Develop the business logic
Open API design allows you to write business logic using a variety of languages.
You can use Swagger Codegen or similar tools to generate boilerplate code based on your design. The tool provides the necessary scaffolding so that you can focus on logic.
Write API documentation
You can use different API documentation tools to auto-generate docs from your OpenAPI definition layer. Your docs should provide developers with all the necessary information to consume your API:
- Authentication scheme;
- Endpoints definition (their function and relations to other endpoints);
Supported HTTP requests and responses;
- All interfaces, classes, constructors, and exceptions;
- Methods, structure, and accepted parameters for each URI; and
- Error descriptions.
If you develop a private API, you can get away with simple reference documentation.
With a public API, the quality of your documentation will directly influence its adoption rate. So provide the best documentation you can, supported by examples, SDKs, and tutorials.
At some point, you’ll likely want to expand the functionality of your API.
It’s critical to ensure that these changes don’t break the apps that rely on the API.
Versioning allows you to specify the resources and features exposed by your API so that users can direct requests to a particular version of a resource/feature. You can include a new version in the request header or in your URL (e.g. yoursite.com/v1/users).
And don’t forget to check your API for backward compatibility.
To make changes easier on developers, you can add a mediation layer to serve as a single point of service for different versions of your API. It can also provide higher scalability, security, and simplify the developer experience.
Each version of your API should get its own documentation.
Now that you know how to write an API, it’s time to…
With API virtualization, you can start testing your API before its finished. Depending on your priorities, you can perform different types of tests: Validation, Functional, Reliability, Load, Security, etc.
Here are a few general rules for API testing:
- Test API functions in isolation;
- Use realistic data for realistic results;
- Test under a variety of network conditions that users might encounter in production;
- Simulate errors and edge cases by rapidly changing responses; and
- Don’t use live APIs for performance testing.
For a more comprehensive overview, check out our guide to API testing!
And the last but not the least…
Once your API is deployed, you’ll have to monitor its success metrics.
Depending on your goals and the type of your API, you might want to track:
- API uptime;
- Requests per month;
- Monthly unique users;
- Response times;
- Server CPU/memory usage;
- Time to receive the API key;
- Time to first 200 OK response;
- Time to first profitable app;
- Monthly revenue (for monetized APIs), etc.
You’ll also have to collect user feedback and incorporate it into next iterations of your API.
Now that you know how to build an API, it’s time to apply this knowledge in practice. And if you need expert advice or lack manpower for your next project, you can always drop us a line.