At MindK, we develop internal APIs for almost all of our projects. We believe a robust API brings a number of advantages, like higher development speed, better scalability, better security, and even easier GDPR compliance. Based on our 12 years of experience, we want to share a detailed 5-step guide on how to make an API.
Table of contents:
- Start with your goals and intended users
- Design the API architecture
- Develop your API
- Test your API
- Monitor your API and iterate on feedback
Planning your API strategy is essential for its long-term success. The API should bring value to both its intended users and your organization. For example, a private API will only get used by the engineers inside of your company. In this case, you have a better understanding of its target audience. Public APIs, on the other hand, can be used by anybody who has a key to your API. To satisfy their needs, you’ll need more information about your target audience:
- Who are the developers that could benefit from your API (their domain, needs, goals, and so on)?
- How can you incorporate their needs into your API?
- How can you provide a better developer experience?
- Tools you need to provide along with your API (developer programs, SDKs, documentation, educational resources, and so forth).
Understanding the needs of users will help define the API requirements. There are two types of requirements to consider. Functional requirements will determine the things your API will be able to do — they are the business capabilities your API will make available to its users. By contrast, your non-functional requirements will deal with things like performance, reliability, and security.
Before writing the first line of code, you should come up with an architecture that fits your requirements and reflects the needs of developers that will use the API. All APIs have to meet 5 non-functional requirements:
- Usability: developers should be able to learn and use your API with minimum effort.
- Reliability: the API should have minimal downtime.
- Scalability: the system should handle load spikes.
- Testability: testers should be able to easily identify any defects.
- Security: the API should be protected from malicious users.
Here’s how to design an API that satisfies these requirements.
1. Separate API design into several layers
At MindK, we recommend splitting your API into 3 layers, each responsible for a single requirement. These layers (depicted in the picture below) 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 – REST and SOAP. Below you can find their main differences:
|Simple Object Access Protocol (SOAP)||Representational State Transfer (REST)|
|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.|
|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.|
|Provides data as services (verbs + nouns – https://my-api/get-user-data).||Provides data as representations of resources (nouns only – https://my-api/users).|
|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, representing over 70% of public APIs. At MindK, we prefer its ease of work, better performance, and scalability. Its great flexibility provides more freedom to create an API as long as your architecture follows six constraints that make it truly REST-ful:
- Uniform interface: there must be a unified way to interact with your server. In other words, requests from different clients (for example, a mobile app and a website) will look similar. One resource in your system must have a single name, called Uniform Resource Identifier, that will be referenced in API requests (https://my-api/users).
- Statelessness: as servers store no information about previous interactions, each API request should provide the necessary context.
- Separation of concerns: the app’s backend should be developed independently from its user interface.
- Caching of responses: servers should inform clients whether the response can be stored in cache.
- Multiple communication layers between the server and the client.
- 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. A human-readable JSON format is more lightweight than XML, easier to parse, and works with all programming languages.
If your API needs to work with both JSON and XML (for example, for legacy systems), you can change output depending on the requested format via request headers.
Following the OpenAPI Specification is the best way to design REST APIs. It’s a widely accepted and language-agnostic standard for building an API interface. 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
Poorly designed APIs can be a major source of vulnerabilities – improper authentication, API keys in URI, unencrypted sensitive data, injections, replay attacks, stack trace leaks, DDoS attacks, and so on. So pay strict attention to security at the design stage by incorporating these 4 security layers:
- Identification (who is accessing your API)
You can use unique randomized identifiers called API keys to identify developers accessing your API. These IDs can help detect “unlawful” behavior.
As API keys aren’t encrypted, you’ll need other security measures to protect your API. Moreover, sending such keys in a Uniform Resource Identifier (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.
- Authentication (proving who you really are)
You can use OpenID for authentication. It redirects developers to an authorization server where they can confirm their identity with a combination of login + password.
- Authorization (limiting what you are allowed to do)
Authenticated users need a list of permissions that match their access level. OAuth2 is our preferred authorization method. It’s faster and more secure than other mechanisms as it relies on tokens instead of usernames and passwords.
- Encryption (making sure the data is unintelligible to unauthorized users)
It’s recommended to use SSL/TLS encryption to protect API traffic against certain attacks like credential hijacking and eavesdropping. You should also use end-to-end encryption for sensitive data like medical records or payment details. You can use tokenization or mask the data from appearing in logs and trace tools.
Security is often built into API frameworks. At MindK, we like to use NestJS to develop internal APIs for our web and mobile apps. In addition to its excellent security, it features Typescript support, greater flexibility, and a large community. Another part of security comes from the way you deploy your APIs. We prefer AWS deployment for its large-scale security features.
Choo i Skyen is a SaaS learning management system we developed for Norwegian associations. Its public API allows to register and promote educational courses on 3-rd party websites.
After you’ve finished your API design, it’s time to build your own API. This is an iterative process. We like to build our APIs one endpoint at a time, gradually adding more functionalities, testing them, and writing detailed documentation.
1. Define all API responses
Depending on the request, your API can return a successful response or an error of some type. Either way, it’s important to standardize responses so they can be processed in a standard way by the client.
You can start by defining the successful response. It will usually contain a status code (for example, 201: resource created OK), a time stamp, and requested data (usually, in the JSON format). You can view all the status codes in the picture below:
2. Handle exceptions and errors
Your API should properly handle all exceptions and return correct HTTP status codes instead of a generic “500: Internal Error”. If the API can’t complete an action due to an exception, it’s recommended to describe the error in the response message.
Be careful as APIs can leak sensitive information in error messages – names of servers, frameworks, classes, versions, and SQL queries used on the project. Hackers can use this to exploit known vulnerabilities in the aforementioned resources. To counter this, use an API gateway which standardizes error messages and avoids exposing sensitive information.
An error message exposing a framework used on the project; source: troyhunt.com
3. Build an API endpoint
Simply put, an API endpoint is one end of a communication channel – a URL that receives your API requests:
While developing an API endpoint, you’ll need to specify the types of requests it can receive, its responses, and errors. With the REST architecture, your endpoints can receive requests that contain different HTTP methods:
- The GET method is used to read resources from your database. As GET can’t modify data, it’s considered a safe method.
- POST is used to create a new subordinate resource in your database.
- PUT is used to update the whole resource.
- PATCH is used to update a part of a resource.
- DELETE is used to delete a resource.
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.
After you’ve built an endpoint, it’s important to check whether it’s behaving as expected by writing a Unit and Integration test.
4. Implement pagination and search by criteria (as part of GET requests)
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, split the data into several “pages”. For easier navigation, each page should have its own URI. The API should only display a portion of data in one go and let users know how many pages remain. 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.
5. Analyse your API for performance
The API should be fast to provide adequate developer experience. But before making any optimization efforts, it’s important to analyze your API performance. You can insert a statement about code usage and performance analysis like Clinic.js for Node.js or php profilers for PHP.
When you’re aware of your performance indicators, start improving them. For example, 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. Particularly large resources can be partitioned for faster service.
Clinic.Js performance analysis. Source: clinicjs.org
6. Implement client-side caching (if needed for performance or other reasons)
Storing data for subsequent requests can speed up the API and save traffic as users 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.
7. Write API documentation
You can use different API documentation tools to auto-generate docs from your OpenAPI definition layer. The 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.
When developing a private API, you can get away with simple reference documentation. With a public API, the quality of documentation will directly influence its adoption rate. So provide the best documentation possible, supported by examples, SDKs, and tutorials.
Generating API documentation using SwaggerHub platform. Source: swagger.io
8. Add versioning (for a public API)
At some point, you’ll likely want to expand the functionality of your API. It’s critical to ensure these changes don’t break the apps that rely on the API.
Versioning allows you to specify the resources and features exposed by the API so that users can direct requests to a particular version of a resource/feature. Include a new version in the request header or in your URL (for example, yoursite.com/v1/users).
And don’t forget, make sure programs that work with one version of your API will be able to use future versions (backwards compatibility).
To make changes easier on developers, 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.
9. Use throttling
Sudden increases in traffic can disrupt your API – a tactic often used in Denial of Service (DDoS) attacks. To prevent this, use:
- Traffic quotas – a limit on the number of requests an app can make per hour/week/month.
- Spike arrests – set a rate at which an app can make requests per minute/second. Calls that exceed this limit get their speed reduced.
- Concurrent rate limits – an app can’t make more than x parallel connections to your API.
How API throttling works. Source: microsoft.com
With API virtualization, you can start testing your API before it’s finished. In addition to Unit and Integration tests, you can perform Functional, Reliability, Load, Security, and other kinds of tests. 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!
When done with testing and reviewing, it’s time to deploy your API to production. Most enterprise APIs are hosted on API gateways that guarantee high security, performance, and scalability.
Once the 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 changes into next iterations of your API.
APIs are essential for modern web development. They are a connecting tissue that allows different software components to talk to each other.
You can follow the five steps outlined in this article to build an API one endpoint at a time. Start by defining your requirements, design the API architecture, detail its responses and error messages, build the endpoint, test, and carefully document it.
Now that you know how to build an API it’s time to apply this knowledge in practice. If you need some advice or lack manpower for your next project, you can always rely on MindK. We’ve been building APIs for the past 12 years for clients from all over the world. We’ll gladly share our expertise with you. Just fill the contact form and we’ll arrange a free consultation with our API expert.