DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Implementing WOPI Protocol For Office Integration
  • Composite Requests in Salesforce Are a Great Idea
  • Serverless Patterns: Web

Trending

  • How to Create a Successful API Ecosystem
  • Build a Simple REST API Using Python Flask and SQLite (With Tests)
  • How to Build Real-Time BI Systems: Architecture, Code, and Best Practices
  • Code Reviews: Building an AI-Powered GitHub Integration
  1. DZone
  2. Data Engineering
  3. Databases
  4. REST API Design Best Practices for Parameters and Query String Usage

REST API Design Best Practices for Parameters and Query String Usage

In this article, take a look at REST API design best practices for parameters and query string usage.

By 
Kay Ploesser user avatar
Kay Ploesser
DZone Core CORE ·
Apr. 10, 20 · Opinion
Likes (9)
Comment
Save
Tweet
Share
83.8K Views

Join the DZone community and get the full member experience.

Join For Free

When we’re designing APIs, the goal is to give our users some amount of power over the service we provide. While HTTP verbs and resource URLs allow for some basic interaction, oftentimes, it’s necessary to provide additional functionality or else the system becomes too cumbersome to work with.

An example of this is pagination: we can’t send every article to a client in one response if we have millions in our database.

A way to get this done is with parametrization.

What Is Parametrization?

Generally speaking, parametrization is a kind of request configuration.

In a programming language, we can request a return value from a function. If the function doesn’t take any parameters, we can’t directly affect this return value.

The same goes for APIs, especially stateless ones like REST APIs. Roy Fielding said this eloquently:

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 own use-cases and rules.

The simplest way to add in all parameter data is to put everything in the body. Many APIs work this way. Every endpoint uses POST and all parameters are in the body. This is especially true in legacy APIs that accumulated more and more parameters over a decade or so, such that they no longer fit in the query string.

While this is more often the case than not, I’d consider it an edge case in API design. If we ask the right questions up front, we can prevent such a result.

What Kind of Parameters Do We Want to Add?

The first question we should ask ourselves is what kind of parameter we want to add?

Maybe it’s a parameter that is a header field already standardized in the HTTP specification.

There are many standardized fields. Sometimes we can reinvent the wheel and add the information to another place. I’m not saying we can’t do it differently. GraphQL, for example, did what I’d consider crazy things from a REST perspective, but it still works. Sometimes it’s just simpler to use what’s already there.

Take for example the Accept header. This allows us to define the format, or media type, the response should take. We can use this to tell the API that we need JSON or XML. We can also use this to get the version of the API.

There is also a Cache-Control header we could use to prevent the API from sending us a cached response with no-cache, instead of using a query string as cache buster (?cb=<RANDOM_STRING>)

Authorization could be seen as a parameter as well. Depending on the detail of authorization of the API, different responses could result from authorized or unauthorized. HTTP defines an Authorization header for this purpose.

After we check all the default header fields, the next step is to evaluate if we should create a custom header field for our parameter, or put it into the query string of our URL.

When Should We Use the Query String?

If we know the parameters we want to add don’t belong in a default header field and aren’t sensitive, we should see if the query string is a good place for them.

Historically, the use of the query string was, as the name implies, to query data. There was a <isindex> HTML element that could be used to send some keywords to a server, and the server would respond with a list of pages that matched the keywords.

Later, the query string was repurposed for web-forms to send data to a server via a GET request.

Therefore, the main use-case of the query string is filtering and specifically two special cases of filtering: searching and pagination. I won’t go into detail here, because we’ve already tackled them in this article.

But as repurposing for web-forms shows, it can also 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.

Should such a parameter go into a custom header or the query string is mostly a question of developer experience.

The HTTP specification states that header fields are kind of like function parameters, so they are indeed thought of as the parameters we want to use. However, adding a query string to an URL is quickly done and more obvious than creating a customer header in this case.

These fields act as request modifiers, with semantics equivalent to the parameters on a programming language method invocation.

Parameters that stay the same on all endpoints are better suited for headers. For example, authentication tokens get sent on every request.

Parameters that are highly dynamic, especially when they’re only valid for a few endpoints, should go in the query string. For example, filter parameters are different for every endpoint.

Bonus: Array and Map Parameters

One question that often crops up is what to do about array parameters inside the query string?

For example, if we have multiple names we want to search.

One solution is the use of square brackets:

Java
 




x


 
1
/authors?name[]=kay&name[]=xing



But the HTTP specification states:

A host identified by an Internet Protocol literal address, version 6[RFC3513] or later, is distinguished by enclosing the IP literal within square brackets (“[” and “]”). This is the only place where square bracket characters are allowed in the URI syntax.

Many implementations of HTTP servers and clients don’t care about this fact, but it should be kept in mind.

Another solution that is offered is simply using one parameter name multiple times:

Java
 




xxxxxxxxxx
1


 
1
/authors?name=kay&name=xing



This is a valid solution but can lead to a decrease in developer experience. Oftentimes, clients just use a map-like data structure, that goes through a simple string conversion before being added to the URL, potentially leading to overriding the following values. A more complex conversion is needed before the request can be sent.

Another way is to separate the values with , characters, which are allowed unencoded inside URLs.

Java
 




xxxxxxxxxx
1


 
1
/authors?name=kay,xing



For map-like data structures, we can use the . character, which is also allowed unencoded.

Java
 




xxxxxxxxxx
1


 
1
/articles?age.gt=21&age.lt=40



It is also possible to URL-encode the whole query string so that it can use whatever characters or format we want. It should be kept in mind that this can also decrease developer experience quite a bit.

When Shouldn’t We Use the Query String?

The query string is part of our URL, and our URL can be read by everyone sitting between the clients and the API, so we shouldn’t put sensitive data like passwords into the query string.

Also, developer experience suffers greatly if we don’t take URL design and length seriously. Sure, most HTTP clients will allow a five-figure length of characters in an URL, but debugging such kinds of strings is not very pleasant.

Since anything can be defined as a resource, sometimes it can make more sense to use a POST endpoint for heavy parameter usage. This lets us send all the data in the body to the API.

Instead of sending a GET request to a resource with multiple parameters in the query string, that could lead to a really long undebuggable URL, we could design it as a resource (e.g. search-resource). Depending on the things our API needs to do to satisfy our request, we could even use this to cache our computation results.

We would POST a new request to our /searches endpoint, that holds our search configuration/parameters in the body. A search ID is returned, which we can use later to GET the results of our search.

Conclusion

As with all best practices, our job as API designers and architects isn’t to follow one approach as “the best solution” but to find out how our APIs are used.

The most frequent use cases should be the simplest to accomplish and it should be really difficult for a user to do something wrong.

Thus, it’s always important to analyze our API usage patterns right from the start - the earlier we have data, the easier it is to implement changes if we messed up our design. Moesif’s analytics service can help with that.

If we go one way because it’s simpler to grasp or easier to implement, we have to look at what we get out of it.

As nested resources can be used to make URLs more readable, they can also become too long and unreadable if we nest too many. The same goes for parameters. If we find ourselves creating one endpoint that has a huge query string, it might be better to extract another resource out of it and send the parameters inside the body.

API Database Strings Data Types REST Web Protocols Design Requests

Published at DZone with permission of Kay Ploesser. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Implementing WOPI Protocol For Office Integration
  • Composite Requests in Salesforce Are a Great Idea
  • Serverless Patterns: Web

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!