REST API Design Best Practices for Rest API Query Parameters and Query String Usage
When we design APIs, the goal is to give our users some control over the service we provide. While HTTP verbs and resource URLs give us some basic control, sometimes we need to provide more functionality through API requests by using different parameter types in an HTTP request, or else the system becomes too hard to work with.
Key Takeaways
- Leverage query parameters: For filtering, sorting, pagination, and non-sensitive data.
- Favor standard headers: Utilize existing HTTP headers for common functionalities (e.g., Accept, Cache-Control, Authorization).
- Choose wisely between custom headers and query strings:
- Query strings are ideal for variable, endpoint-specific, and non-sensitive parameters.
- Custom headers are suitable for parameters shared across endpoints.
- Prioritize readability: Keep URLs concise and easy to understand.
- Ensure security: Never transmit sensitive data via query parameters.
- Handle array and map parameters: Employ commas, delimiters, or dot notation for clear representation.
- Avoid excessive nesting and long URLs: Refactor complex queries into dedicated endpoints or use POST requests with parameters in the body.
What is Parametrization in Path Parameters
In general, parametrization is a kind of request configuration.
In programming, we can ask a function for a specific result by providing input values, called parameters. However, if a function doesn’t have parameters, we can’t directly influence the result it gives back.
The same goes with APIs, especially stateless ones like REST APIs. Roy Fielding said it best: All REST interactions are stateless. That is, each request contains all of the information necessary for a connector to understand the request, independent of any requests that may have preceded it. There are many ways in HTTP to add parameters to our request: the query string, the body of POST, PUT, and PATCH requests, and the header. Each has its use cases and rules. When considering these methods, it’s important to evaluate the query options available to us to ensure optimal API design.
Sometimes, the easiest way to send all the information you need is to just put everything in the “body” of the message, like stuffing a bunch of items into an envelope. Many older APIs work this way, where everything is a POST request and all the details you want to send are included within the body. This happens when an API has been around for a while and has collected so many parameters (think of these as specific details) that they no longer fit into the smaller “query string” area of a request.
While this is more common than not, I’d consider this an edge case in API design. If we ask the right questions upfront we can prevent this. Using API query parameters allows users to specify sorting order, category of items, and other specific content or actions. Caching mechanisms can store and serve the same data for subsequent requests with identical query parameters,
What kind of query parameters do we want to add?
The first question we should ask ourselves is what kind of parameter we want to add. We should also consider the parameter values that will be used, as they can affect the behavior and performance of our API. When thinking about what kind of parameter to add, we should also think about the query options available to us.
Maybe it’s a parameter that is a header field already standardized in the HTTP specification
We don’t always need to reinvent the wheel when it comes to APIs. There are many standard fields that can be used for various purposes. Sometimes it might seem tempting to do things differently, and that’s okay – look at GraphQL, which took a unique approach compared to REST, but it still works! However, sticking with established conventions can often make things simpler.
Take the “Accept” header, for example. It’s like telling the API your preferred language – do you want the response in JSON or XML? You can even use it to specify which version of the API you’re using.
Another handy header is “Cache-Control,” which acts like a reminder not to give you an outdated response. Instead of adding extra bits to the web address (like “?cb=<RANDOM_STRING>”), you can simply use this header to request fresh data.
Even authorization, which determines who can access what can be seen as a parameter. Different levels of authorization can lead to different responses. Luckily, HTTP already has an “Authorization” header for this very purpose.
So, while there’s room for innovation, sometimes it’s just easier and more efficient to use the tools that are already available.
When dealing with POST, PUT, and PATCH requests we often need to include data in the request body. The request body parameters carry the actual payload or data being sent to the server, commonly used for transmitting data like JSON objects, form data, or file uploads. These body parameters are used to send the necessary data along with the request in the body of the HTTP message.
Once we’ve explored all the standard options, it’s time to make a choice: should we create a special, custom header field to carry our parameter, or should we tuck it into the query string at the end of our web address?
When should we use the query string?
If the extra details we want to send don’t fit in any of the standard headers and aren’t confidential, let’s consider adding them to the query string. It’s like attaching a note to the envelope instead of putting it inside with the letter. A query parameter is a key-value pair attached to the end of a URL to customize API calls.
In the olden days of the internet, the query string was exactly what it sounds like - a way to ask the server for specific information. Imagine typing some keywords into a search box and hitting “Enter” – the query string would carry those keywords to the server, which would then respond with a list of matching web pages.
Later on, the query string found a new job. It became a way to send information from web forms using a GET request. But even with this new task, the query string’s main purpose is still about filtering. It helps us narrow down the results we want to see, especially when it comes to searching (like in a search engine) or moving through pages of results (like when you click “Next Page” on a website). Think of it as a way to refine your search or navigate through a large collection of information. I won’t go into detail here, because we’ve already covered them in this article.
But as repurposing for web forms shows, it can be used for different types of parameters. A RESTful API could use a POST or PUT request with a body to send form data to a server. One example would be a parameter for nested representations. By default, we return a plain representation of an article. When a ?withComments query string is added to the endpoint we return the comments of that article in-line, so only one request is needed. GET requests with query params are safe, cacheable, and easy to construct.
Should we put this parameter in a custom header or the query string is mostly a matter of developer experience? Multiple query parameters can be combined to apply various query filters at the same time, enhancing the flexibility and specificity of API calls.
You could say that the HTTP specification treats header fields like parameters we give to a function in programming. This means we could technically use headers to send our extra information. But let’s be honest, sometimes it’s just quicker and easier to tack it onto the end of the web address as a query string. It’s like jotting a quick note on the outside of an envelope rather than adding it to the letter itself.
While these headers function similarly to parameters in a programming language, adding a query string is often a more straightforward and visible solution for simple additions to a request. Parameters that are the same on all endpoints are better suited for headers. For example, authentication tokens are sent on every request.
If we have parameters that change a lot, or ones that are only relevant to a few specific endpoints, it’s best to put them in the query string. Think of it like a personalized note you only want certain people to see – it’s better to attach it to the outside of the envelope rather than including it in the main letter. Filter parameters are a good example of this, as they vary depending on the specific information you’re trying to find.
Bonus: Array and Map Parameters
One question that often comes up is what to do about array parameters inside the query string. Understanding the query syntax is crucial for using array parameters inside the query string. Key-value pairs are used to customize API calls, to filter, sort, and paginate. They are like ‘magic charms’ that grant special powers to the queries.
For example, if we have multiple names we want to search.
One solution is to use square brackets. /authors?name=kay&name=xing
Now, the official HTTP rules say that square brackets have a very specific purpose in web addresses: they’re like name tags for IPv6 addresses (a newer type of internet address). This means they should only be used in that particular context.
While many servers and browsers are a bit more flexible and won’t complain if you use square brackets elsewhere, it’s still important to follow the rules. It’s like wearing a tuxedo to a casual party – you might not get kicked out, but it’s not exactly following the dress code.
Another solution is to simply use one parameter name multiple times: /authors?name=kay&name=xing
Using square brackets might seem like a quick fix, but it can actually make things more complicated for developers. Think of it like using a shortcut that ends up causing more trouble down the road.
Many developers organize parameters in a way that’s easy to understand, like using a map or a list. But if these parameters get converted into a simple string with square brackets, it can mess up the order and even overwrite some values. This means more work is needed to clean up the request before it’s ready to be sent.
A better approach is to use commas or other allowed characters to separate the values. It’s like using dividers in a drawer to keep things organized. This way, the parameters stay in order and the request is easier to read and understand.
For map-like data structures, we can use the. character, which is also allowed unencoded. /articles?age.gt=21&age.lt=40
There’s another option: we can scramble the entire query string using URL encoding, which lets us use any characters or format we want. It’s like putting your message into a secret code that only the server can decipher. But, just like a secret code, this can make things a bit harder for developers to understand and work with.
When shouldn’t we use the query string?
Think of the query string as a postcard attached to your letter. Anyone who handles the mail can read what’s on it, so it’s not the best place to put sensitive information like passwords or credit card numbers. A well-thought-out query structure is crucial to avoid putting sensitive data in the query string. Proper error handling is crucial, including clear and informative error messages and proper HTTP status codes.
Think of a really long, complicated URL like a tangled mess of yarn. It might work, but it’s a nightmare for developers to untangle and figure out. Even though most tools can handle super long web addresses, it’s not exactly a pleasant experience trying to debug them.
Since pretty much anything can be treated as a “resource” in the API world, sometimes it makes more sense to use a POST request instead of a GET when you have a lot of parameters. This way, all the data is neatly packed into the body of the request, like a well-organized package.
Instead of creating a monster URL with tons of parameters, we can design a dedicated endpoint, like “/searches.” Then, we can send a POST request to this endpoint with all our parameters tucked away in the body. The API can even use this information to remember the results and give them back to us later if we ask for the same search again. This is like saving your work so you don’t have to start from scratch every time.
The key takeaway here is that there’s no one-size-fits-all solution. As API designers, our job is to figure out how our APIs are actually being used and choose the best approach for each situation. It’s like being a tailor, creating custom-fit solutions instead of forcing everyone to wear the same generic outfit. The use of URL path parameters is important for identifying specific resources and is different from query parameters in terms of position and usage.
The most common use cases should be the simplest to do and it should be really hard for a user to do something wrong.
So it’s always important to analyze our API usage patterns from the very start - the earlier we have data the easier it is to change if we mess up our design. Moesif’s analytics service can help with that. Moesif is the most advanced API analytics service used by thousands of platforms to measure their customers’ usage. If we go one way because it’s simpler to understand or easier to implement we have to look at what we get out of it.
Imagine nesting resources in a URL like stacking boxes. A few boxes might be fine, making things organized and easy to find. But if you keep stacking, it becomes a towering mess, hard to manage, and even harder to look at. The same goes for query strings – too many parameters make the URL unwieldy and difficult to read.
If you find yourself building a URL that resembles a never-ending story, it might be time to reconsider. Instead of cramming everything into a single endpoint with a massive query string, why not create a separate resource to handle those parameters? Think of it like splitting up a giant box into smaller, more manageable ones.
This approach not only makes your URLs cleaner and more readable but also improves the overall structure of your API. It’s like organizing your belongings – a little bit of effort upfront saves you a lot of headaches later on.
Conclusion
In conclusion, mastering the art of API parametrization is a crucial skill for crafting intuitive and powerful APIs. By understanding the nuances of query parameters, path parameters, and custom headers, you can design interfaces that cater to a wide range of user needs. Remember, a well-designed API is not just about functionality; it’s about empowering users to interact with your service seamlessly and efficiently. Prioritize simplicity, flexibility, and robustness, and always consider the user experience when making design decisions. By doing so, you’ll create APIs that are both developer-friendly and capable of delivering the precise data and actions that users seek.
Organizations looking for the best tools to support their API management can leverage Moesif’s powerful API analytics and monetization capabilities. Moesif easily integrates with your favorite API management platform or API gateway through one of our easy-to-use plugins, or embed Moesif directly into your API code using one of our SDKs. To try it yourself, sign up today and start with a 14-day free trial; no credit card is required.