Computer scienceBackendDjangoAPI

REST API design principles

10 minutes read

In software development, you often come across the term API. API stands for Application Programming Interface. It's a tool that lets two applications communicate with each other. APIs give you an easy way to get and share data within and outside companies. More specifically, an API is a set of rules and protocols that enable different software systems to interact. It acts as a bridge, allowing developers to use the functions and data of external services or components. This provides a standard method to ask for, send, and receive information. APIs are essential in modern software development, as they allow different systems to connect smoothly, from web applications to operating systems. Let's take a look at the following examples:

  • You've probably used a weather application on your browser and phone. The data they show is the same, but the way it's presented varies. This is an API in action.

  • The navigation app you use daily calls an API to get live traffic updates and uses another API to update and synchronize map data.

  • Fitness apps and wearable devices collect various health data like the number of steps, heart rate, sleep patterns, and calories burned. APIs allow these apps to link with health databases or platforms so that you can see all your health information in one place.

So, whenever you have more than one service that needs to communicate or sync up, you need a way for them to talk to each other; this is where APIs come in. In this topic, you will learn why APIs are important for your projects, different design approaches, and other key basics about APIs.

Why do you need an API in your projects?

Imagine you're at a restaurant and you order some food. You sit in the dining area while your food is made in the kitchen. How will your order reach the kitchen, and how will your food come to your table? There are waiters who serve you. An API acts like a waiter for your requests and responses. In software development, different components act like waiters; they each have their own way of talking and understanding things.

In today's software world, it's important for things to work on different platforms. To do this, you must separate the frontend of an application from its backend. APIs act as a go-between here. They also let different systems talk to each other. Developers use APIs to connect easily to outside services and let different apps share information. This makes software work better, speeds up making things, reduces doing the same work over, and helps create new things. APIs let developers use the good parts of other platforms and services, which means better and more powerful software.

For instance, if you're making an app that needs real-time weather data, what do you do? You might not be able to get this data on your own, or you might have to write extra scripts to grab it from the internet. But you could use APIs from weather services and put them into your project. That way, you won't have to worry about how to get the data and can focus more on your project. You can see this in other things like adding payment options and using online services.

import requests
weather_api_url = "https://api.exampleweather.com/current"
params = {"city": "London", "api_key": "your_api_key_here"}
response = requests.get(weather_api_url, params=params)
if response.status_code == 200:
    weather_data = response.json()
    print(f"Current weather in {weather_data['location']}:")
    print(f"Temperature: {weather_data['temperature']}°C")
    print(f"Condition: {weather_data['condition']}")
else:
    print("Failed to retrieve weather data”) 

In this example, you use a GET request to get current weather data for a specific city from the weather API. The parameters include the city name and an API key. If the response is good (status code 200), it is turned into JSON to pull out important weather details such as temperature and conditions. This shows how APIs make it easy to add outside services, like weather services, to a Python script, letting developers include current weather data in their apps.

API design approaches

Now that you have a better understanding of APIs and what they do, let's look at the different kinds of APIs. Depending on the specific needs and requirements of the project, your API design can follow various approaches. Although APIs serve similar functions, some distinctions set each apart. You will look at three popular API design approaches in this topic.

  1. SOAP Simple Object Access Protocol, or SOAP, is a protocol that uses XML for message formatting and operates over protocols such as HTTP, SMTP, UDP, and others. It's structured and well-defined, making it suitable for complex enterprise-level applications. It also contains ACID (atomicity, consistency, isolation, and durability) compliance, so it offers good reliability for use in banking systems.

  2. REST Representational State Transfer, or REST, is an architectural style that uses JSON for message formatting and relies solely on standard HTTP methods. It's known for its simplicity and scalability. An example is a social media platform's API that fetches user profiles or posts using REST. REST is preferred for simplicity and fast development, especially in web applications.

  3. GraphQL GraphQL is an open-source query language for APIs. It allows users to request only the data they need, reducing over-fetching and under-fetching. Imagine a product catalog where different clients need various details about a product. GraphQL is beneficial when you need flexibility in data retrieval, enabling efficient and targeted queries.

The table below gives a general overview of the API design approaches:

SOAP

REST

GraphQL

Data Format

XML

Any (typically JSON or XML)

JSON

Protocol

HTTP, SMTP, XMPP, more

HTTP

Any (typically HTTP/HTTPS)

Communication Type

Synchronous

Synchronous and Asynchronous

Synchronous and Asynchronous

Service Definition

WSDL (Web Services Description Language)

None

Schema Definition Language (SDL)

Over-fetching/Under-fetching issues

Yes

Yes

No (allows the client to specify exactly what it needs)

Error Handling

WS-Standard Status/Error codes

HTTP Status codes

GraphQL Errors in the response body

Caching

Not built-in; implementation is needed

Built-in (HTTP caching)

Not built-in; implementation is needed

Learning Curve

High

Low

Medium

Type Safety

Yes

No

Yes

Real-time Data

No

Possible through long polling or WebSockets

Yes (with subscriptions)

Versioning

Changes are made through new endpoints

Changes are made through new endpoints

Changes are made in the schema; old fields can be deprecated

Elements of PATH in your API

In web development, services talk to each other using URLs, and the PATH in these API services acts as a vital part that defines how you interact with the API. API design includes PATH components and query parameters. These help you interact with the API effectively. The PATH works as an address that lets you reach an API and use its features.

PATH components are a key part of the URL. They show the order and connection between resources. For example, in the URL "https://hyperskill.org/knowledge-map/1162", "knowledge-map" is a PATH component that points to the type of resource, like a map for courses or projects, and "1162" identifies a specific item within that resource. In REST APIs, providing an identifier in a path isn't the only option; you can also provide it as a parameter in the request payload.

Query parameters, meanwhile, offer more dynamic and flexible ways to interact. They often help filter, sort, or customize the data the API sends back. For example, in the URL "https://hyperskill.org/categories?name=python", "name" is a query parameter that filters the categories by their name.

While creating APIs, you can also add the API version to the path. It's a common way to handle updates and changes. The URL "https://hyperskill.org/v1/knowledge-map/1162" marks version 1 of the API, making sure old versions still work while adding new functions or improvements.

The design of your API's PATH is very important. You should follow the guidelines when developing your API. The URL patterns above show that you can tell what resource or feature you're using from the URL alone. When working with APIs, keeping things consistent is key. The specific rules you use for naming things aren't as important as being consistent. Choosing between single or multiple forms for your entities isn't about right or wrong. Once you choose, you must use the same style everywhere. This makes everything predictable and simple to understand, no matter which style you pick.

REST Verbs in theory

Now let's dive deeper into RESTful APIs. They follow principles that assign specific HTTP verbs to perform different actions. Here's a look at some of these verbs: GET, POST, PUT, and DELETE.

The GET verb is for retrieving information from the server. It is idempotent, which means making the same GET request repeatedly has the same effect as making it once. In RESTful terms, a GET request is for safe operations; it should not change the server's state.

GET /users

This retrieves a list of users from the server.

GET /users/1

This retrieves a user with ID 1 from the server.

The POST verb is for submitting data to be processed. It is often used to create a new instance on the server. POST requests are not idempotent; they may have side effects, like creating duplicates if sent more than once.

POST /users

This would create a new user based on the data in the request body. The data is sent as parameters in the request body.

The PUT verb acts like POST but updates existing data. It submits data to be processed, but instead of creating new data, it changes mutable fields of an entity and updates the instance to reflect those changes.

PUT /users/123

This would update the user with ID 123 using the data in the request body.

The DELETE verb is for requesting the removal of a resource from the server. Like GET, it is idempotent. After a successful DELETE request, the resource should not exist on the server anymore.

DELETE /users/123

This would delete the user with ID 123. Typically, only authenticated and authorized users send DELETE requests. If our authentication system is weak, it can lead to serious security issues.

When to Use Each Verb:

  • Use GET for: Retrieving data from the server, like getting a list of users or fetching details of a specific user.

  • Use POST for: Creating a new resource on the server, such as adding a new user.

  • Use PUT for: Updating a resource on the server. It's for sending the complete updated resource.

  • Use DELETE for: Removing a resource from the server. It means the client wants to delete the resource.

Using these HTTP verbs properly in a RESTful API design leads to a clear and consistent way to handle different operation types on resources.

JSON as a lingua franca of web API

JSON, or JavaScript Object Notation, has become the standard language of web APIs, offering a popular data format for communication on the modern web. Its widespread use comes from several important qualities. JSON is light, flexible, and simple to parse, making it a top choice for sending data between web servers and clients. Because it's readable by humans, it allows for easy debugging, and its ability to work across many platforms and languages shows its adaptability.

In Python, the built-in JSON library has methods like json.dumps to change Python objects into JSON format and json.loads to change JSON back into Python objects. For example:

import json
python_dict = {"name": "John", "age": 30, "city": "New York"}
json_data = json.dumps(python_dict)
decoded_dict = json.loads(json_data)

You should know an important difference between Python dictionaries and JSON objects. Although their structures look alike, JSON objects follow the JSON rules strictly, which means keys must be strings, and they only allow a certain set of data types. This strict format helps different systems and languages work together smoothly, adding to JSON's role as the go-to language for web APIs.

An API response or JSON response may not always be a dictionary. Look at these examples:

# Example 1: List of dictionaries 
python_list_of_dicts = [{"name": "John", "age": 30, "city": "New York"}, {"name": "Jane", "age": 25, "city": "Chicago"}]
json_data = json.dumps(python_list_of_dicts)
decoded_list_of_dicts = json.loads(json_data)
print(decoded_list_of_dicts)

# Example 2: Nested dictionary 
python_nested_dict = {"person": {"name": "John", "age": 30}, "city": {"name": "New York", "population": 8000000}}
json_data = json.dumps(python_nested_dict)
decoded_nested_dict = json.loads(json_data)
print(decoded_nested_dict)

# Example 3: Dictionary with tuple keys 
python_dict_with_tuple = {("John", 30): "New York"}
try:
    json_data = json.dumps(python_dict_with_tuple)
except TypeError:
    print("JSON does not support tuples as keys. This will raise a TypeError.")

# Example 4: Dictionary with complex values (other dicts or lists)
python_dict_with_complex_values = {"name": "John", "age": 30, "cities_lived": ["New York", "San Francisco", "Austin"]}
json_data = json.dumps(python_dict_with_complex_values)
decoded_dict_with_complex_values = json.loads(json_data)
print(decoded_dict_with_complex_values)

You can index into the response if it comes as a list of dictionaries, a method often used in development. Example 3 shows the difference between JSON objects and Python dictionaries. In Python, a tuple can be a dictionary key because it is hashable, but JSON only supports strings as keys due to its formatting rules.

To sum up, JSON's ease of use for humans and compatibility with numerous platforms and languages make it a practical and widely accepted choice for web API data serialization.

API limitations to think of

When you develop an API, you need to be mindful of several limits that can greatly affect performance, scalability, error handling, caching, data validation, and overall user experience. For example, when crafting an API, consider setting a rate limit; you might need to limit how many requests a client can make within a certain time frame. This is essential to prevent abuse, ensure fair use, and protect the API from potential misuse or too much traffic. Likewise, data validation is vital to confirm that incoming data fits expected formats and standards, preventing incorrect or harmful data from harming the system's integrity. It helps keep data quality high and protects the application from potential security threats.

Another crucial aspect to consider is your API documentation. You should document your API clearly so that others can use it easily. You should also pay attention to the behavior of the API you are using. For instance, REST is stateless, so if tracking state is important, then it's up to the API consumer to manage this. You should also include this in the API documentation. Last, you should remember that using an API might cost you money, so you need to know who to bill. Therefore, you must have some way to identify and authorize users, like headers with an API_KEY, or by checking active and refresh tokens. Let's look at an example:

import requests

API_KEY = 'your_api_key'

# The URL of the API you're trying to access
url = 'https://api.example.com/data'

headers = {
    'Authorization': f'Bearer {API_KEY}',
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
    print('Request was successful.')
    data = response.json()  # Convert the response to JSON
else:
    print(f'Request failed with status code {response.status_code}'

In this example, the API_KEY is included in the headers of the HTTP request. When the API receives the request, it checks the Authorization header to verify the API key. If valid, the API processes the request and charges the account associated with the API key.

Please note, the exact authentication method can vary based on the API. Some APIs might need you to add the API key as a query parameter in the URL, while others might use a different header or a different format for the Authorization header. Always consult the API's documentation to make sure you use the right authentication method.

Usually, you don't need to set up these limits and protections yourself if you use a stable framework for APIs. Most security risks are handled by the frameworks.

Conclusion

In conclusion, designing a REST API requires careful choice-making. REST APIs work with HTTP protocols and methods like GET, POST, PUT, DELETE to make interactions with resources clear and predictable. Formats such as JSON are popular for their ease of use and wide support. Important factors include checking data to make sure it’s in the right format, setting limits on how often people can make requests to prevent system overload, breaking down data into smaller chunks for easier management, and using caching to speed things up. Good error management, consistent with HTTP status codes, helps users fix issues. Thus, a well-designed REST API follows these principles but also changes to meet the varied needs of the internet, ensuring it runs smoothly.

3 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo