
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@rescale/nemo
Advanced tools
A middleware composition library for Next.js applications that allows you to organize and chain middleware functions based on URL patterns.
A middleware composition library for Next.js applications that allows you to organize and chain middleware functions based on URL patterns.
npm install @rescale/nemo
pnpm add @rescale/nemo
bun add @rescale/nemo
This example shows all possible options of NEMO usage and middlewares compositions, including nested routes:
import { createNEMO } from '@rescale/nemo';
export default createNEMO({
// Simple route with middleware chain
'/api': [
// First middleware in the chain
async (request, { storage }) => {
storage.set('timestamp', Date.now());
// Continues to the next middleware
},
// Second middleware accesses shared storage
async (request, { storage }) => {
const timestamp = storage.get('timestamp');
console.log(`Request started at: ${timestamp}`);
}
],
// Nested routes using object notation
'/dashboard': {
// This middleware runs on /dashboard
middleware: async (request) => {
console.log('Dashboard root');
},
// Nested route with parameter
'/:teamId': {
// This middleware runs on /dashboard/:teamId
middleware: async (request, { params }) => {
console.log(`Team dashboard: ${params.teamId}`);
},
// Further nesting with additional parameter
'/users/:userId': async (request, { params }) => {
console.log(`Team user: ${params.teamId}, User: ${params.userId}`);
}
},
// Another nested route under /dashboard
'/settings': async (request) => {
console.log('Dashboard settings');
}
},
// Pattern matching multiple routes
'/(auth|login)': async (request) => {
console.log('Auth page');
}
});
Each middleware in a chain is executed in sequence until one returns a response or all are completed. Nested routes allow you to organize your middleware hierarchically, matching more specific paths while maintaining a clean structure.
When a request matches a nested route, NEMO executes middleware in this order:
before
middleware (if defined)/
) for all non-root requestsmiddleware
property)after
middleware (if defined)If any middleware returns a response (like a redirect), the chain stops and that response is returned immediately.
NextMiddleware
type NextMiddleware = (
request: NextRequest,
event: NemoEvent
) => NextMiddlewareResult | Promise<NextMiddlewareResult>;
The standard middleware function signature used in NEMO, compatible with Next.js native middleware.
MiddlewareConfig
type MiddlewareConfig = Record<string, MiddlewareConfigValue>;
A configuration object that maps route patterns to middleware functions or arrays of middleware functions.
GlobalMiddlewareConfig
type GlobalMiddlewareConfig = Partial<
Record<"before" | "after", NextMiddleware | NextMiddleware[]>
>;
Configuration for global middleware that runs before or after route-specific middleware.
createNEMO
function createNEMO(
middlewares: MiddlewareConfig,
globalMiddleware?: GlobalMiddlewareConfig,
config?: NemoConfig
): NextMiddleware
Creates a composed middleware function with enhanced features:
NemoConfig
optionsinterface NemoConfig {
debug?: boolean;
silent?: boolean;
errorHandler?: ErrorHandler;
enableTiming?: boolean;
storage?: StorageAdapter | (() => StorageAdapter);
}
To make it easier to understand, you can check the below examples:
Matches /dashboard
route exactly.
/dashboard
Path parameters allow you to capture parts of the URL path. The general pattern is :paramName
where paramName
is the name of the parameter that will be available in the middleware function's event.params
object.
Named parameters are defined by prefixing a colon to the parameter name (:paramName).
/dashboard/:team
This matches /dashboard/team1
and provides team
param with value team1
.
You can also place parameters in the middle of a path pattern:
/team/:teamId/dashboard
This matches /team/123/dashboard
and provides teamId
param with value 123
.
You can include multiple parameters in a single pattern:
/users/:userId/posts/:postId
This matches /users/123/posts/456
and provides parameters userId: "123", postId: "456"
.
Parameters can have a custom regexp pattern in parentheses, which overrides the default match:
/icon-:size(\\d+).png
This matches /icon-123.png
but not /icon-abc.png
and provides size
param with value 123
.
Parameters can be suffixed with a question mark (?
) to make them optional:
/users/:userId?
This matches both /users
and /users/123
.
Parameters can be wrapped in curly braces {}
to create custom prefixes or suffixes:
/product{-:version}?
This matches both /product
and /product-v1
and provides version
param with value v1
when present.
Parameters can be suffixed with an asterisk (*
) to match zero or more segments:
/files/:path*
This matches /files
, /files/documents
, /files/documents/work
, etc.
Parameters can be suffixed with a plus sign (+
) to match one or more segments:
/files/:path+
This matches /files/documents
, /files/documents/work
, etc., but not /files
.
You can match multiple pattern alternatives by using parentheses and the pipe character:
/(auth|login)
This matches both /auth
and /login
.
The matcher fully supports Unicode characters in both patterns and paths:
/café/:item
This matches /café/croissant
and provides item
param with value croissant
.
You can constrain route parameters to match only specific values or exclude certain values:
// Match only if :lang is either 'en' or 'cn'
const nemo = new NEMO({
"/:lang(en|cn)/settings": [
// This middleware only runs for /en/settings or /cn/settings
(req) => {
const { lang } = req.params;
// lang will be either 'en' or 'cn'
return NextResponse.next();
},
],
});
// Exclude specific values from matching
const nemo = new NEMO({
"/:path(!api)/:subpath": [
// This middleware runs for any /:path/:subpath EXCEPT when path is 'api'
// e.g., /docs/intro will match, but /api/users will not
(req) => {
const { path, subpath } = req.params;
return NextResponse.next();
},
],
});
import { createNEMO } from '@rescale/nemo';
export const middleware = createNEMO({
// Simple route
'/api': async (request) => {
// Handle API routes
},
// With parameter
'/users/:userId': async (request, event) => {
// Access parameter
console.log(`User ID: ${event.params.userId}`);
},
// Optional pattern with custom prefix
'/product{-:version}?': async (request, event) => {
// event.params.version will be undefined for '/product'
// or the version value for '/product-v1'
console.log(`Version: ${event.params.version || 'latest'}`);
},
// Pattern with custom matching
'/files/:filename(.*\\.pdf)': async (request, event) => {
// Only matches PDF files
console.log(`Processing PDF: ${event.params.filename}`);
}
});
import { createNEMO } from '@rescale/nemo';
export default createNEMO({
'/api{/*path}': apiMiddleware,
},
{
before: [loggerMiddleware, authMiddleware],
after: cleanupMiddleware,
});
The Storage API allows you to share data between middleware executions:
import { createNEMO } from '@rescale/nemo';
export default createNEMO({
'/': [
async (req, { storage }) => {
// Set values
storage.set('counter', 1);
storage.set('items', ['a', 'b']);
storage.set('user', { id: 1, name: 'John' });
// Check if key exists
if (storage.has('counter')) {
// Get values (with type safety)
const count = storage.get<number>('counter');
const items = storage.get<string[]>('items');
const user = storage.get<{id: number, name: string}>('user');
// Delete a key
storage.delete('counter');
}
}
]
});
Access URL parameters through the event's params property:
import { createNEMO } from '@rescale/nemo';
export default createNEMO({
'/users/:userId': async (request, event) => {
const { userId } = event.params;
console.log(`Processing request for user: ${userId}`);
}
});
NEMO provides built-in logging capabilities through the event object that maintains consistent formatting and respects the debug configuration:
import { createNEMO } from '@rescale/nemo';
export default createNEMO({
'/api': async (request, event) => {
// Debug logs (only shown when debug: true in config)
event.log('Processing API request', request.nextUrl.pathname);
try {
// Your API logic
const result = await processRequest(request);
event.log('Request processed successfully', result);
return NextResponse.json(result);
} catch (error) {
// Error logs (always shown)
event.error('Failed to process request', error);
// Warning logs (always shown)
event.warn('This endpoint will be deprecated soon');
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
}, undefined, { debug: true });
All logs maintain the "[NEMO]" prefix for consistency with internal framework logs.
I'm working with Next.js project for a few years now, after Vercel moved multiple /**/_middleware.ts
files to a single /middleware.ts
file, there was a unfilled gap - but just for now.
After a 2023 retro I had found that there is no good solution for that problem, so I took matters into my own hands. I wanted to share that motivation with everyone here, as I think that we all need to remember how it all started.
Hope it will save you some time and would make your project DX better!
FAQs
A middleware composition library for Next.js applications that allows you to organize and chain middleware functions based on URL patterns.
We found that @rescale/nemo demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.