Learning how to create an API is essential for modern web development. Application Programming Interfaces, or APIs, help us integrate third-party payment features, connect enterprise systems, and exchange medical data securely.
At MindK, we develop internal APIs for almost all projects. As a CTO and a software engineer with 15 years of experience, I believe a well-designed API brings a number of advantages. They include higher development speed, better scalability, data security, and even easier compliance with regulations like GDPR and HIPAA.
So, here’s my detailed guide on how to make an API that provides these benefit
Step #1. Planning and strategy
The API should bring value to both intended users and your organization. So, start with your goals and intended users.
For example, a private API will only get used by the engineers inside your company. In this case, you’ll have a better understanding of its target audience. Public APIs, on the other hand, can be used by anybody who has the API key.
To satisfy their needs, you’ll need more information about the 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 the API?
- How can you provide a better developer experience?
- Which do tools you need to provide along with the API (developer programs, SDKs, documentation, educational resources, and so forth)?
Understanding user needs helps to define the API requirements. There are two types of requirements to consider.
- Functional requirements determine the things your API can do. They are the business capabilities your API makes available to its users.
- Non-functional requirements deal with things like performance, reliability, and security.
Step #2. API design
Before writing the first line of code, you should come up with an architecture that fits your requirements. Likewise, it should reflect the needs of developers that will use the API.
There are two broad approaches to API development. The first is the backend for front-end design (BFF). It includes a BFF orchestration layer for each of your front-ends. A well-designed BFF is simple, fast, and resilient. However, it’s only tailored to one specific front-end.
This is often a problem for larger projects. For example, we recently built an AI-powered app for rapid drug testing. The team, however, thought about expanding the app to include up to 400 healthcare services in the future. So, starting with a universal API, well-defined limits and a detailed description made more sense.
The second approach is using an API Gateway. It serves as a single point of connection between all clients. We prefer this approach for its consistency, reusability, and future-proof design. It also improves developer experience across companies.
Regardless of your architectural style, all APIs have to meet five 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) 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
There are several common approaches to API architecture, including the outdated SOAP as well as modern REST and GraphQL. You can find their main differences below:
SOAP | REST | Graph QL |
An official protocol with strict guidelines. | A flexible architectural style with a number of loose guidelines. | A complex query language with flexible and powerful functionality. |
Works with application layer protocols like HTTP, UDP, and SMTP. | Works only with HTTP. | Operates over HTTP. |
Requests can’t be cached. | Requests can be cached. | Limited caching support. |
Requires detailed API contracts. | No detailed contracts needed. | No detailed contracts needed. |
Has built-in security, error handling, and authorization. | Developers have to take care of security, error handling, and authorization. | Developers have to take care of security, error handling, and authorization. |
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. | Primarily uses JSON. |
Provides data as services (verbs + nouns: https://my-api/get-user-data). | Provides data as representations of resources (nouns only: https://my-api/users). | Clients define exactly what data they need (nouns) and how it should be fetched or modified (verbs) |
➕Benefits: security is easier to implement. | ➕Benefits: higher performance, scalability, and flexibility. | ➕Benefits: flexible, scalable, easy for new team members to understand. |
➖Drawbacks: difficult data processing, XML formatting mistakes can cause difficult to diagnose errors, additional libraries needed. | ➖Drawbacks: requires versioning, lacks strong typing, prone to under– and over-fetching. | ➖Drawbacks: N+1 performance problem, ill-suited for external integrations, security requires a lot of limitations. |
✅Great for: legacy enterprise apps with high security requirements. That’s all. | ✅Great for: microservices, internal and public APIs for web and mobile apps. | ✅Great for: internal APIs, data-driven apps, and real-time updates. |
3. Make your API REST-ful
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. Requests from different clients (for example, a mobile app and a website) should look similar. One resource in your system must have a single name, called Uniform Resource Identifier. It will be referenced in all API requests (https://my-api/users).
- Statelessness. Servers store no information about previous interactions. So, each API request should provide the necessary context.
- Separation of concerns. Develop the app’s backend 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.
REST relies on a familiar HTTP protocol, so developers can get up to speed much faster. A human-readable JSON format is lighter than XML, easier to parse, and works with all programming languages.
If an API needs to work with JSON and XML (for example, in legacy systems), you can change output via request headers depending on the requested format.
The best way to design REST APIs is to follow the OpenAPI Specification. 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.
4. Think about security
Poorly designed APIs can be a major source of vulnerabilities. They include improper authentication, API keys in URI, unencrypted sensitive data, injections, replay attacks, stack trace leaks, DDoS attacks, and so on.
So, how to secure a REST API? Incorporate these four security layers during the design phase:
- Identification (who is accessing your API)
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, other security measures to protect your API are required. 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 network logs.
- Authentication (can they prove their identity)
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 (what are they 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)
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.
Choo i Skyen is a scalable LMS with a microservice architecture we developed for Norwegian associations. Its public REST API allows to register and promote educational courses on 3-rd party websites.
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 excellent security, it features Typescript support, greater flexibility, and a large community.
Additional security comes from the way your APIs are deployed. We prefer AWS deployment for its large-scale security features. The cloud provider also has some nice security features like AWS Cognito and API Gateway.
Step #3. API development
After finishing your API design, it’s time to build your own API. This is an iterative process. We like to build one feature-oriented endpoint at a time with basic functionality. Then, gradually add more fields or parameters, test them, and write 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.
Start by defining the successful response. It usually contains 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, 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 that standardizes error messages and avoids exposing sensitive information.
3. Build an API endpoint
Simply put, an API endpoint is one end of a communication channel. It’s a URL that receives API requests:
https://my-api/this-is-an-endpoint
https://my-api/another/endpoint
https://my-api/some/other/endpoint
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:
- GET to read resources from your database. As GET can’t modify data, it’s considered a safe method.
- POST to create a new subordinate resource in your database.
- PUT to update the whole resource.
- PATCH to update a part of a resource.
- DELETE to delete a resource.
GET, PUT, DELETE, HEAD, and PATCH requests must be idempotent. This means that repeating the same call to the same resource must lead to the same state. Always use the plural for your resources and follow standard naming conventions for consistency.
After you’ve built an endpoint, 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, thousands of products can be relevant to a search in an e-commerce app. Sending all 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).
- Default values.
For easier sorting, use various filters like time of creation or price, and implement search via a query string.
5. Analyze your API 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 and AutoCannon for Node.js.
Also use HTTP compression to cut the size of large objects. Combining compression with streaming can reduce latency even further. Massive resources can be partitioned for faster service.
You can also use HTTP compression to cut the size of large objects. Combining compression with streaming can reduce latency even further. Massive resources can be partitioned for faster service.
This is just a small part of backend performance testing and optimization we do at MindK. If your application is sluggish and slow to respond, our engineers can run a variety of tests load, stress, volume, endurance tests; as well as assess your databases and cloud infrastructure.
As a result, you’ll get a detailed roadmap to optimize database configurations, server resources, caching strategies; auto-scaling policies, asset delivery, code refactoring, lazy loading, and infrastructure costs.
Clinic.Js performance analysis. Source: clinicjs.org
6. Implement client-side caching, if needed
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 and when it will be considered expired.
7. Create API documentation
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.
- Error descriptions.
For a private API, simple reference documentation is all that’s required.
The quality of documentation for a public API will directly influence its adoption rate, so provide the best documentation possible, supported by examples, SDKs, and tutorials
AI-driven development isn’t yet mature enough to develop complex APIs. However, generative AI may help speed up the Swagger documentation.
Generating API documentation using SwaggerHub platform. Source: swagger.io
8. Add versioning (for public API)
At some point, you’ll likely want to expand the API functionality. It’s critical to ensure these changes don’t break the apps that rely on your API.
Versioning allows you to specify the resources and features exposed by the API. Users can direct requests to a particular version of a resource/feature.
Always include a new version in the request header or in your URL (for example, yoursite.com/v1/users). Programs that work with one version of your API should be able to use its future versions (backwards compatibility).
Adding a mediation layer makes changes easier on developers. It serves as a single point of service for different versions of your API. It’s also good for scalability, security, and developer experience.
9. Use throttling
Sudden traffic increases can disrupt your API. This tactic is a part of Denial of Service (DDoS) attacks. To prevent this, use:
- Traffic quotas. Limit the number of requests an app can make per hour/week/month.
- Spike arrests. 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
Step #4. API testing
With API virtualization, you can start testing the API before it’s finished. In addition to Unit and Integration tests, you can perform Functional, Reliability, Load, Security, and other 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.
- Don’t use live APIs for performance testing.
For a more comprehensive overview, check out our guide to API testing.
Step #5. API monitoring and improvement
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.
After deploying your API, you’ll have to monitor its success metrics. Depending on the goals and type of 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 for the first 200 OK response.
- Monthly revenue (for monetized APIs).
We use tools like Postman Monitoring, Uptrends, and Amazon CloudWatch (AWS-only) to monitor real-time response time, performance, uptime, availability, and more. Another task is to collect user feedback and incorporate changes into the next iterations of your API.
How to create an API: conclusion
APIs are essential for custom application development. They are a connecting tissue that allows different software components to talk to each other.
You can use these five steps as guidance on how to build an API, one endpoint at a time. Start by defining your requirements. Then, design the API architecture, detail the responses and error messages, build the endpoint, test, and carefully document it. And don’t forget to take care of the API security!
Now, it’s time to apply this knowledge. If you need some advice or lack experienced developers for your next project, you can always rely on MindK. We’ve been building complex web and mobile applications for Healthcare, Education, Fintech, and other industries.
So feel free to get in touch to arrange a free no-obligations consultation with our experts.