Simple Requests in Frontend Development

As a Senior Frontend Developer, working with APIs is part and parcel of daily life. When dealing with cross-origin requests, understanding the concept of a “simple request” is essential. This article will explore what a simple request is, its criteria, and provide examples using TypeScript.
What is a Simple Request?
In the context of Cross-Origin Resource Sharing (CORS), a simple request is a type of HTTP request that adheres to specific conditions laid out by the CORS specification. Simple requests are designed to be straightforward and avoid the need for a preflight request — an additional request made by the browser to check permissions before sending the actual request.
Preflight requests add overhead, so simple requests can be more efficient when your request meets the necessary criteria.
Criteria for a Simple Request
To qualify as a simple request, three key conditions must be met:
- HTTP Methods
The request must use one of the following HTTP methods:
GET
POST
HEAD
2. Headers
- Only certain headers are allowed in the request. These are known as the CORS-safelisted request headers and include:
Accept
Accept-Language
Content-Language
Content-Type
(with additional restrictions; see below)
3. Content-Type Restrictions
- If the request includes a
Content-Type
header, its value must be one of the following: text/plain
application/x-www-form-urlencoded
multipart/form-data
4. No Custom Headers
- The request must not include any custom headers beyond those specified above.
5. No Credentials (Optional)
- If credentials such as cookies, HTTP authentication, or client-side SSL certificates are included, they must be explicitly allowed by the server with the
Access-Control-Allow-Credentials
header.
If these criteria are met, the browser will send the request directly without initiating a preflight.
Simple Request Example with TypeScript
Let’s see how this works in practice using TypeScript.
Example 1: Making a GET Request
async function fetchUserData(userId: string): Promise<any> {
const response = await fetch(`https://example.com/api/users/${userId}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Error fetching user data: ${response.statusText}`);
}
return response.json();
}
fetchUserData('123')
.then(userData => console.log(userData))
.catch(err => console.error(err));
This is a simple request because:
- It uses the
GET
method. - The
Accept
header is a safelisted header. - No custom headers are added.
Example 2: Making a POST Request with application/x-www-form-urlencoded
async function submitForm(data: { name: string; email: string }): Promise<any> {
const formBody = new URLSearchParams();
formBody.append('name', data.name);
formBody.append('email', data.email);
const response = await fetch('https://example.com/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formBody.toString(),
});
if (!response.ok) {
throw new Error(`Error submitting form: ${response.statusText}`);
}
return response.json();
}
submitForm({ name: 'John Doe', email: 'john.doe@example.com' })
.then(response => console.log(response))
.catch(err => console.error(err));
This is a simple request because:
- It uses the
POST
method. - The
Content-Type
isapplication/x-www-form-urlencoded
, which is safelisted. - No custom headers are added.
When Does a Request Fail to Qualify as Simple?
Any deviation from the criteria will result in a request being considered “non-simple,” triggering a preflight request. For example:
- Using a method like
PUT
orDELETE
. - Adding custom headers such as
Authorization
. - Using a
Content-Type
likeapplication/json
(this is NOT safelisted).
Here’s an example of a non-simple request:
async function updateUser(userId: string, data: { name: string; email: string }): Promise<any> {
const response = await fetch(`https://example.com/api/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>'
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Error updating user: ${response.statusText}`);
}
return response.json();
}
updateUser('123', { name: 'Jane Doe', email: 'jane.doe@example.com' })
.then(response => console.log(response))
.catch(err => console.error(err));
This request will trigger a preflight because:
- The method
PUT
is not allowed for simple requests. - The
Content-Type
header is not safelisted. - A custom
Authorization
header is included.
Conclusion
Simple requests are an efficient way to interact with APIs while avoiding the overhead of preflight requests. By understanding their criteria and tailoring your frontend code accordingly, you can optimize your application’s performance and reduce unnecessary complexity.
Whenever possible, structure your requests to qualify as simple — especially for high-frequency operations such as fetching data or submitting forms. And, as always, ensure that your server’s CORS configuration aligns with your application’s requirements.