π₯ ESLint plugin for Hono and all Hono lovers
In order for this plugin to become a major release, it's extremely important that you experiment with it.
Please try it out and incorporate it into your Hono project.
Please also create issues with any bugs, improvement requests, and new feature suggestions.
npm install -D eslint-plugin-hono@alphaTo use the recommended configuration, create an eslint.config.js file in your project root and add the following:
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import hono from "eslint-plugin-hono";
export default [
{
plugins: {
hono: hono,
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...hono.configs.recommended,
{
files: ["**/*.{ts,tsx,cts,mts}"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
},
globals: globals.node,
},
},
];If you want to apply the rules only to specific files, you can use the files property:
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import hono from "eslint-plugin-hono";
export default [
{
plugins: {
hono: hono,
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...hono.configs.recommended,
{
files: ["**/*.{ts,tsx,cts,mts}"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
},
globals: globals.node,
},
rules: {
// custom rules
}
},
];| Rule | π¨ error | π§ fix | |
|---|---|---|---|
| route-grouping | β | β | |
| prefer-http-exception | β | ||
| param-name-mismatch | β | ||
| no-multiple-next | β | ||
| no-unused-context-response | β | ||
| no-process-env | β | ||
| global-middleware-placement | β |
Enforce grouping and ordering of routes by HTTP method and Hono instance.
This rule enhances code organization by checking three things:
- Instance Grouping: All route definitions for a specific Hono instance must be contiguous. Once you start defining routes for another instance, you cannot add more routes to the previous one.
- Path Grouping: Routes for the same path (e.g.,
/users) must be grouped together. - Method Order: Within a path group, methods must follow a consistent order (e.g.,
getbeforepost).
Note: app.route() calls are excluded from these checks. Method chains (e.g., .get(...).post(...)) are exempt from method order checking.
{
"hono/route-grouping": ["error", {
"order": [
"use",
"all",
"get",
"post",
"put",
"patch",
"delete",
"options",
"on"
]
}]
}order: (array of strings, optional) Specifies the desired order of HTTP methods. The default order is ["use", "all", "get", "post", "put", "patch", "delete", "options", "on"].
Incorrect Path Grouping
const app = new Hono();
app.get('/path1', (c) => c.text('get'));
app.get('/path2', (c) => c.text('get'));
app.post('/path1', (c) => c.text('post'));Correct
const app = new Hono();
app.get('/path1', (c) => c.text('get'));
app.post('/path1', (c) => c.text('post'));
app.get('/path2', (c) => c.text('get'));Incorrect Method Order
const app = new Hono();
app.post('/path1', (c) => c.text('post'));
app.get('/path1', (c) => c.text('get'));Correct
const app = new Hono();
app.get('/path1', (c) => c.text('get'));
app.post('/path1', (c) => c.text('post'));Incorrect Instance Grouping
const books = new Hono();
const users = new Hono();
books.get('/books', (c) => c.text('get books'));
users.get('/users', (c) => c.text('get users'));
books.post('/books', (c) => c.text('create book')); // Error: books routes should be togetherCorrect
const books = new Hono();
const users = new Hono();
books.get('/books', (c) => c.text('get books'));
books.post('/books', (c) => c.text('create book'));
users.get('/users', (c) => c.text('get users'));Suggest using HTTPException instead of generic Error for HTTP errors.
This rule detects when a standard Error is thrown with a message that corresponds to a standard HTTP status code (e.g., "Not Found", "Unauthorized"). In Hono applications, it is better to use HTTPException to return proper HTTP status codes.
Incorrect
throw new Error('Not Found');
throw new Error('Unauthorized');
throw new Error('Bad Request');Correct
import { HTTPException } from 'hono/http-exception';
throw new HTTPException(404, { message: 'Not Found' });
throw new HTTPException(401, { message: 'Unauthorized' });
throw new HTTPException(400, { message: 'Bad Request' });Ensure parameter name in c.req.param() matches the route definition.
This rule checks that the parameter names used in c.req.param('name') call inside a route handler match the parameters defined in the route path (e.g., /posts/:postId). This prevents runtime errors caused by typos or mismatched parameter names.
Incorrect
const app = new Hono();
app.get('/posts/:postId', (c) => {
const id = c.req.param('id'); // 'id' is not defined in '/posts/:postId'
return c.text(id);
});Correct
const app = new Hono();
app.get('/posts/:postId', (c) => {
const postId = c.req.param('postId');
return c.text(postId);
});Disallow multiple calls to next() in a single middleware execution path.
Hono middleware relies on await next() to pass control to the next middleware. Calling next() multiple times in the same middleware function will cause a runtime error ("next() called multiple times"). This rule detects and prevents such patterns.
Incorrect
const middleware = async (c, next) => {
await next();
await next(); // Error
};const middleware = async (c, next) => {
if (condition) {
await next();
}
await next(); // Error if condition is true
};Correct
const middleware = async (c, next) => {
await next();
};const middleware = async (c, next) => {
if (condition) {
await next();
} else {
await next();
}
};Disallow unused calls to Context response methods (c.json, c.text, etc.).
In Hono, methods like c.json() create a response object but do not send it automatically. If the return value is not returned from the handler (or awaited/used), the request might hang or result in a 404.
Incorrect
app.get('/', (c) => {
c.json({ message: 'hello' }); // return is missing!
});Correct
app.get('/', (c) => {
return c.json({ message: 'hello' });
});Disallow the use of process.env in favor of c.env.
This rule enforces the use of c.env for accessing environment variables within Hono handlers instead of process.env. Using c.env ensures your application remains platform-agnostic, as it abstracts away environment-specific details (e.g., Cloudflare Workers bindings vs. Node.js process.env).
Incorrect
const app = new Hono();
app.get('/', (c) => {
const apiKey = process.env.API_KEY; // Disallowed
return c.text(apiKey);
});Correct
const app = new Hono();
app.get('/', (c) => {
const apiKey = c.env.API_KEY;
return c.text(apiKey);
});Enforce that global middleware is placed before route definitions.
This rule ensures that global middleware (e.g., app.use(logger) or app.use('*', logger)) is defined immediately after the Hono instance is created, and before any routes (app.get(), app.post(), etc.) are defined. This improves code readability and predictability. Path-specific middleware (e.g., app.use('/admin/*', adminAuth)) is ignored by this rule to allow for logical grouping with the routes it applies to.
Incorrect
const app = new Hono();
app.get('/', (c) => c.text('Hello'));
app.use('*', logger()); // Global middleware defined after a route.Correct
const app = new Hono();
app.use('*', logger());
app.get('/', (c) => c.text('Hello'));
app.use('/admin', adminOnly()); // Path-specific middleware can be defined later.
app.get('/admin/dashboard', (c) => c.text('Dashboard'));Made by Kanon. Publish under MIT License.