ESLint plugin with rules to help you write better fetch() API calls.
Use npm or a compatibility tool to install.
npm install --save-dev eslint @teapot-smashers/eslint-plugin-fetch- Node.js v8.10.0 or newer versions.
- ESLint v5.16.0 or newer versions.
Add the plugin to your ESLint configuration file:
import fetchPlugin from '@teapot-smashers/eslint-plugin-fetch';
export default [
{
plugins: {
fetch: fetchPlugin,
},
rules: {
// Core safety rules
'fetch/require-json-content-type': 'error',
'fetch/require-encoded-query-params': 'error',
'fetch/require-status-check': 'error',
'fetch/require-timeout': 'error',
'fetch/require-error-handling': 'error',
// Code quality rules
'fetch/require-json-response-check': 'error',
'fetch/prefer-async-await': 'warn',
'fetch/no-json-in-get-requests': 'error',
},
},
];{
"plugins": ["@teapot-smashers/fetch"],
"rules": {
"@teapot-smashers/fetch/require-json-content-type": "error",
"@teapot-smashers/fetch/require-encoded-query-params": "error",
"@teapot-smashers/fetch/require-status-check": "error",
"@teapot-smashers/fetch/require-timeout": "error",
"@teapot-smashers/fetch/require-error-handling": "error",
"@teapot-smashers/fetch/require-json-response-check": "error",
"@teapot-smashers/fetch/prefer-async-await": "warn",
"@teapot-smashers/fetch/no-json-in-get-requests": "error"
}
}@teapot-smashers/fetch/recommended- enables all rules with recommended settings
import fetchPlugin from '@teapot-smashers/eslint-plugin-fetch';
export default [
{
plugins: {
fetch: fetchPlugin,
},
extends: ['fetch/recommended'],
},
];{
"extends": ["plugin:@teapot-smashers/fetch/recommended"]
}| Rule ID | Description | Fixable | Recommended |
|---|---|---|---|
| require-json-content-type | Require Content-Type header when sending JSON data | π§ | β |
| require-encoded-query-params | Require proper encoding of query parameters | β | |
| require-status-check | Require checking response.ok or response.status | β | |
| require-timeout | Require timeout configuration to prevent hanging | β | |
| require-error-handling | Require error handling with try/catch or .catch() | β |
| Rule ID | Description | Fixable | Recommended |
|---|---|---|---|
| require-json-response-check | Check Content-Type before parsing JSON responses | β | |
| prefer-async-await | Prefer async/await over promise chains | ||
| no-json-in-get-requests | Disallow JSON body in GET requests | β |
The fetch() API has several gotchas that can lead to bugs, security issues, and poor user experience:
- Missing Content-Type headers - Servers may not parse JSON correctly without proper headers
- Unencoded query parameters - Can lead to injection attacks and broken URLs
- No automatic error handling -
fetch()doesn't throw on HTTP error status codes - No built-in timeouts - Requests can hang indefinitely, leading to poor UX
- Unhandled promise rejections - Network errors can crash applications
- Unsafe JSON parsing -
response.json()throws on non-JSON responses - Complex promise chains - Hard to read and debug compared to async/await
- HTTP semantics violations - Using JSON bodies in GET requests
This plugin helps catch these issues early in development, making your fetch code more robust, secure, and maintainable.
// β Multiple issues in one function
async function fetchUserData(userId) {
// Missing timeout, error handling, and status check
const response = await fetch(`/api/users/${userId}`);
// Unsafe JSON parsing without Content-Type check
const user = await response.json();
// Unencoded query parameters in search
const searchResponse = await fetch(`/api/search?q=${user.name}&type=user`);
const results = await searchResponse.json();
return { user, results };
}
// β Promise chain instead of async/await
fetch('/api/data')
.then((response) => response.json()) // No status or content-type check
.then((data) => console.log(data)); // No error handling
// β JSON in GET request
fetch('/api/search', {
method: 'GET',
body: JSON.stringify({ query: 'javascript', filters: ['recent'] }),
});// β
Comprehensive safe implementation
async function fetchUserData(userId) {
try {
// Proper timeout and error handling
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
clearTimeout(timeoutId);
// Status check before processing
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Content-Type check before JSON parsing
const contentType = response.headers.get('content-type');
if (!contentType?.includes('application/json')) {
throw new Error('Response is not JSON');
}
const user = await response.json();
// Properly encoded query parameters
const searchParams = new URLSearchParams({
q: user.name,
type: 'user',
});
const searchResponse = await fetch(`/api/search?${searchParams}`, {
signal: AbortSignal.timeout(3000),
});
if (!searchResponse.ok) {
throw new Error(`Search failed: ${searchResponse.status}`);
}
const results = await searchResponse.json();
return { user, results };
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
console.error('Failed to fetch user data:', error);
throw error;
}
}
// β
Clean async/await with proper error handling
async function fetchData() {
try {
const response = await fetch('/api/data', {
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
const data = await response.json();
console.log(data);
} else {
const text = await response.text();
console.log('Non-JSON response:', text);
}
} catch (error) {
console.error('Fetch failed:', error.message);
}
}
// β
POST request for complex search data
async function searchWithFilters() {
try {
const response = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'javascript',
filters: ['recent', 'popular'],
sort: { field: 'date', order: 'desc' },
}),
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
throw new Error(`Search failed: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Search error:', error);
throw error;
}
}-
Install the plugin:
npm install --save-dev eslint @teapot-smashers/eslint-plugin-fetch
-
Use the recommended config in your
eslint.config.js:import fetchPlugin from '@teapot-smashers/eslint-plugin-fetch'; export default [ { plugins: { fetch: fetchPlugin }, extends: ['fetch/recommended'], }, ];
-
Run ESLint to catch fetch-related issues:
npx eslint your-code.js
Contributions are welcome! Please see our Contributing Guide for details.
MIT