← Back

My Clean Practice to Generate URL Queries

Published

06 October 2023

Tags

JavaScript
TypeScript

Background

When I did code reviews or wrote code that has requirements to add a static URL on it, the most common style that I found is using a string literal like the code below.

const authorizationUri = "https://github.com/login/oauth/authorize";
const clientId = "123";
const redirectUri = "https://app.google.com/callback";
const scope = "repo";

const url = `${authorizationUri}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}`;

// The 'url' output: "https://github.com/login/oauth/authorize?client_id=123&redirect_uri=https://app.google.com/callback&scope=repo"

This kind of implementation has several issues:

  • Prone to mistyping, like forgetting to add the query separator.
  • Forget to add URL encode and when you add it, the code will look uglier and hard to read.
  • Accidentally adding whitespace characters when breaking across multiple lines when writing long URLs.

Solution

Fortunately, JavaScript has a native constructor to generate a URL in a safer way, that is URL(), and a set() method to set the query on the constructed URL.

Let's demonstrate how to use it with the code that has been written above.

const authorizationUri = "https://github.com/login/oauth/authorize";
const clientId = "123";
const redirectUri = "https://app.google.com/callback";
const scope = "repo";
 
const url = new URL(authorizationUri); // construct base URL
url.searchParams.set("client_id", clientId);
url.searchParams.set("redirect_uri", redirectUri);
url.searchParams.set("scope", scope);
 
// The 'url' output: "https://github.com/login/oauth/authorize?client_id=123&redirect_uri=https://app.google.com/callback&scope=repo"

This solution is easier to read and maintain and generating a URL in this way will resolve the issue above:

  • The separator character will always be correct (in the case above, the query will be separated with ? and each query will separate &).
  • Each query is automatically encoded.
  • No need to worry about unwanted whitespace characters when writing long URLs.

Advance

This another solution has the same idea with slightly different implementation from the previous one. We will combine the URLSearchParams with satisfies from TypeScript to create a type-safety on the URLs. By using URLSearchParams, we can define the query at once in one parameter.

For implementation sample:

const authorizationUri = "https://github.com/login/oauth/authorize";
const clientId = "123";
const redirectUri = "https://app.google.com/callback";
const scope = "repo";

const params = new URLSearchParams({
	"client_id": clientId,
	"redirect_uri": redirectUri,
	scope 
});
const url = `${authorizationUri}/?${params}`;

// The 'url' output: "https://github.com/login/oauth/authorize?client_id=123&redirect_uri=https://app.google.com/callback&scope=repo"

Then, how TypeScript will enhance our code above by using satisfies operator? Beside defining a Type that represent the queries key and data type, you have to put the operator beside the parameters like code below.

type URLQueryParams = {
	"client_id": string;
	"redirect_uri": string;
	scope: string;
}

....

const params = new URLSearchParams({
	"client_id": clientId,
	"redirect_uri": redirectUri,
	scope 
} satisfies URLQueryParams);

.....

If the parameter not satisfying the type, an error message will appear like image below. Congrats! Your URL queries now also type-safety. This practice can be used to type-safe in JSON.stringify value and getting autocomplete on API’s which are typed as any.

TypeScript error when not satisfied the type.TypeScript error when the parameter input is not satisfied the type.