model

Even though your API code is super clean and shiny, your customers may never see it. They don’t like your API architecture.

You should try to use clear, explicable names and a schema that is as close to the documentation as possible. In addition, with a few simple rules, you should be able to extend it in the future so that you can continue to add more information to the existing response, rather than eliminating the old version early on.

1. Suggestions for mode implementation

Over the years, the most common pattern-related error I’ve encountered is due to a lack of rigor in the API development environment. When designing an API, using a framework that is appropriate for your programming language will go a long way in helping you do what you’re going to do.

Take some time to explore the options available, and remember that while most Web frameworks provide tools to help you implement REST APIs, you still need to do a lot of the work yourself, and never reinvent the wheel.

Maintain a strict data model layer

The most popular back-end Web technologies (as well as the front-end) are based on loosely typed scripting languages with bad programming habits that are hard to break away from. PHP and JavaScript (in the form of Node.js) are examples of lax datatypes because they are often used without a strict data model.

Be sure to use an internal data layer that is rigorously modeled by ORM or another data model infrastructure for your database. Your API response should be contained in strictly defined nested data. Do not generate the results of database queries into simple data structures. However, keeping a strict data layer is what you should do in all applications.

Data should always be filtered to avoid exposing sensitive information before exposing it to an API response. Any changes made to the fields exposed to the API should be noted for compatibility with the current API version and added to the API update log

2. Good architectural practices

Unified naming

Names for code should be consistent across the entire API, URI, and request and response, and have special meaning. Unclear naming can put a lot of pressure on other developers as well as yourself.

Keep in mind that your URIs and schemas should communicate purpose to users as clearly as possible without them having to constantly browse through the documentation. Don’t be afraid to use long names.

Strict data types

Even if your programming language is not strict, you should use strict data types. A number field should always have only numbers, and a string field should always contain only strings, such as subclasses. You should never mix different data types in the same field. The value for your field may be different, but the data type for that field should be determined.

It is a common error in a loosely typed environment to see the same field as the number 42 in one response, but as the string “42” in another. This approach is inconsistent and difficult to parse again for all client security. In a loosely typed architecture, the client is at risk when parsing every field.

Obviously, this works not only with raw data types (numbers, strings, booleans, and so on), but also with JSON objects and arrays. Do not return an object of type Chair on an object field containing a Table or an array of Cars on a field containing a Bicycles array.

While the above is very basic, many good developers have made similar mistakes.

A strong model layer can help you avoid such embarrassing mistakes.

Don’t Ignore Fields

When a field has no available value, do not ignore it completely. Depending on the data type and the semantics of the missing value, use NULL, empty string, empty array, or zero.

Don’t use 10 interfaces to get all the fields when one interface meets the requirements. Maybe they can look it up in the documentation, but why not make the documentation easier to understand? Keep in mind that documentation becomes stale much faster than code, which is what generates the architecture.

Again, this type of error is much easier to avoid if you use a powerful model layer in your implementation.

Don’t abuse JSON objects

I’ve seen it more often than I can remember in API requests and responses, and this is often the result of bad practices associated with loosely typed languages.

Suppose you have a People object with a unique ID as the primary key and a child object as the value

{
    "people": {
        "1234": {
            "name": "John",
            "surname": "Smith"
        },
        "5678": {
            "name ": "John ",
            "surname": "Smith"
        }
    }
}

The People object changes with its internal content data types, in this case, its keys are 1234 and 5678, but no one knows what its keys will be the next time it is requested.

This is a terrible thing to do, and when parsed in any strictly typed language, it results in inconsistent and generally bad code. Each JSON object in the API should always have an unchanging set of strictly defined fields when requested.

Here’s a good use case for arrays that simply returns an array and includes the ID in each array element.

{
    "people": [
        {
            "id": 1234,
            "name": "John",
            "surname": "Smith"
        },
        {
            "id": 5678,
            "name": "John",
            "surname": "Smith"
        }
    ]
}

Typically, JSON abuse occurs when you try to make it easier to find your back-end code by using a field with a unique ID key. You must keep in mind that in this case, the details of the internal implementation can be leaked to the user – a phenomenon that should be avoided in all aspects of software development.

Don’t abuse JSON arrays

If you followed the previous advice and changed some objects to arrays, you’re doing well!

Now, you must ensure that the array contains only one type of object. Don’t mix apples and oranges! Keep in mind that not all clients store data in loosely typed containers, and parsing heterogeneous resource lists is not only inconsistent and annoying, but also unsafe.

When you really can’t avoid returning different types of entities in the same array, try returning a list of super objects that are abstract enough to describe the properties of all the object types that you need to return.

In the case of apples and oranges, perhaps you should return a Fruit object. A Fruit object can contain all the attributes of the Apple and Organge objects, as well as a field that can accurately describe the type of Fruit for each object.

If you return items whose properties are completely different for each returned type, but still have to return them to the same list, you may have to use extreme measures, such as container objects. It’s not a very elegant solution, but it’s necessary.

Here’s an example of a container object:

Your interface will return a list of the aircraft a person owns, along with some basic characteristics of each aircraft. A flight can be an airplane or a hot air balloon, and there is a big difference between the two. Semantically, it makes little sense to add attributes such as wingspan, number of engines, or horsepower to a hot air balloon, and it makes no sense to add attributes such as blue, balloon material, and balloon shape to the aircraft.

It makes no sense to add all these property fields to a single object type. Instead, you can store the aircraft object and the hot air balloon object in a container object.

In this case, a container object of type Vehicle(essentially a supertype) would contain two fields Airplane and Ballon, each corresponding to a different child object. Remember that even if one of the fields has no data, the field and its data type are returned.

{
    "vehicles": [
        {
            "type": "airplane",
            "airplane": {
                "engines": 1,
                "wing\_span": 12,
                "horsepower": 240
            },
             "balloon": null
        },
        {
            "type": "balloon",
            "airplane": null,
            "balloon": {
                "basket": "rattan",
                "balloon\_material": "dacron",
                "balloon\_shape": "natural"
            }
        }
    ]
}

Again, avoid this design if possible, but if you have to return completely different objects in the same collection, container objects are a good way to maintain a strictly typed schema.

Don’t rely on simple hard-coded error messages

I hate to disappoint you, but no matter how funny and amusing your error messages may be, they are rarely of interest to users. It’s not that other developers don’t appreciate your writing skills, it’s that you never know how a customer will present an error.

In addition, you must always return errors in a concise, machine-readable manner so that they can be easily resolved by the client. You should return the correct HTTP status code and include the specific error message in the error object in the corresponding body.

Here’s an example:

Your user requests an order through the following API

GET /customers/21/order/42

If no customer or order was Found, the response is a 404 Not Found status code, but is that enough? The user will not be able to accurately distinguish the cause of the error, because he does not know whether the error is caused by the customer or the order.

This is where the error object in the body of your response comes in handy.

A readable machine code makes things easier for the client. In addition, having a statement (for example, “customer_not_found”) instead of a number makes it easier for the developer – without having to look at the table of numbers and error instructions in the API documentation.

Finally, the Message field can better explain the cause of the error to developers, so they have a better idea of how to handle the error and where to look for additional information. Ideally, the error should be localized according to the Accepted Language header requested by the customer. Who knows, maybe the end user will read your masterpiece in some way.

Do not use numerical enumeration

As mentioned over and over again, you should have an architecture that is easy to read and self-document. Do not use numbers when enumerating. Use simple strings.

Do you have a type field in your animal object? Do not use 1, 2, 3, 4, and 5 as their values. “Dog”, “cat”, “parrot”, “Armadillo” and “elephant” are much easier to read by humans and for machines that know how to compare strings.

People typically do this when using numerical enumeration internally on the back end, but this is (again) an implementation detail that should not be leaked to API consumers.

I’ve also heard some excuses, such as increasing the bandwidth consumption of string methods, but there are other better ways to solve this problem. Your architecture should be detailed enough to be understood at a glance, and you should use Gzip to reduce bandwidth consumption, which is quite different from saving a few bytes using a digital enumeration.

Do not return an unwrapped JSON array

What exactly is encapsulation (or JSON) in this case? In a nutshell, this means wrapping (or encapsulating) the response data into a JSON object, and then returning it to the data (or something similar) field in the root directory of the response body.

Some people seem to think this is a good practice for all responses, because it allows you to add metadata fields (such as error or paging information) in the future without tampering with the main response object. This does make the API schema cleaner, although it may require more code to parse.

Even if you don’t want to do this for all responses, I believe it’s useful (even necessary) when you return a collection of objects. In this case, you should never use an array as the root container for the response!

The main reason above is that if your root container is a JSON array, your architecture will fundamentally change when the response needs to return an error (which will inevitably be a JSON object). This makes parsing more complex without providing any real benefit.

In addition (even if you ignore the above), arrays make it more likely that the API will be deprecated early, because it can never be changed or modified in any way without deprecating the schema. On the other hand, using an object as the root response container allows you to add as many fields as you want later without incurring deprecation. Heck, you can even return objects of different updated types in the new array, as long as you make sure to keep the old array.

Use a UNIX timestamp or ISO-8601 date

My personal preference has always been to use UNIX timestamps as dates in responses, because they are relatively short and easy to parse. However, unless you are a machine, they are hard to convert and can actually only be read as real dates. ISO-8601 dates, on the other hand, are better from a readability perspective, but a little more difficult to parse (though not much).

Any other string format than these should be avoided at all costs, as it may cause ambiguity when parsing. I know you can specify your own date-time format in the documentation, and clients can parse dates based on that format, but keep in mind that the API should be as easy to understand as possible without much outside help.

Avoid filling large numbers of objects

If object A is not A user; And contains fields such as user_id, user_name, user_favorite_color, user_pet, etc. Maybe it’s time to use A encapsulated User object inside object A…

Since the (database and API) architecture becomes more complex over time, it is best to normalize it from the beginning and keep it as clean as possible.

Use the object for fields that may require more information in the future

Always try to make the API future-oriented in any way possible. Try to preserve properties that are likely to require additional information in the future so that you can extend the life of the main release.

You need to consider:

Assume that each of your Book objects has a Boolean value of is_available. While this is enough to tell us that a book is not available when it is fake, it does not tell us why the book is not available or when it can be used again. In the future, if you want to add this information, you must add it to the Book object by adding two additional fields.

A cleaner approach is to use a Availability field that stores a Availability object that initially contains only the is_available field, but can be modified to include additional information about the book’s Availability (for example, the time stamp for why the book is unavailable and when it will be available again), Without adding more fields to the original schema.

3. Mode enabled

If you use a versioning approach, you can make small incremental changes to the API while maintaining structural stability, but you should be careful with each change.

Some changes to the structure mean that the current major version is immediately deprecated. They should be avoided unless absolutely necessary.

Keep in mind that the changes listed below are not the only cause of schema deprecation (which can often be caused by details in your particular design), but only the most common.

Do not delete fields or change field names

You can never be sure how your users will use your information. No matter how trivial or redundant a field may seem, if you make a mistake when you put it into production, you’ll be stuck with it until the next major release. Changing a field name is obviously the same as deleting a field.

Do not change the data type

The data type must remain strict not only in the response, but also in the minor version. This is another bad practice associated with loosely typed development environments.

You must change the current version to change the data type of the field. If you passed the number as a string in your initial release response, you should always return it as a string until the next major release.

Do not edit existing enumeration cases

It’s common to return a number (remember, you shouldn’t) or a string as an enumeration.

For example, you might use the “Car”, “Truck” and “MOTRCYCLE” fields for the vehicle_type field. If you notice the typos in “Motorcycle”, I know you must be upset, but you can’t fix it! However, you can do this in the next major release (and don’t forget to add it to the change log).