NextRush
API ReferenceMiddleware

@nextrush/cors

Security-hardened CORS middleware with presets and origin validation.

Browsers block cross-origin API requests by default. CORS middleware tells browsers which origins, methods, and headers your API accepts. Without it, frontend applications on different domains cannot reach your endpoints.

This middleware implements the full CORS specification with additional protections against null origin attacks, regex denial-of-service, and credential misconfigurations.

Default Behavior

With no options, cors() sets origin to falseno CORS headers are added and requests pass through unmodified. You must configure at least an origin value for the middleware to take effect.

The middleware always adds Vary: Origin for correct cache behavior, even when the request has no Origin header.

Installation

$ pnpm add @nextrush/cors

Minimal Usage

import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';

const app = createApp();

app.use(
  cors({
    origin: 'https://app.example.com',
  })
);

Configuration Options

CorsOptions

PropertyTypeDescription
originboolean | string | string[] | RegExp | OriginValidator= falseAllowed origin(s). false disables CORS, true reflects the request origin, '*' allows any origin.
methodsstring | string[]= 'GET,HEAD,PUT,PATCH,POST,DELETE'Allowed HTTP methods for preflight requests.
allowedHeaders?string | string[]Allowed request headers. If omitted, mirrors Access-Control-Request-Headers from the preflight.
exposedHeaders?string | string[]Response headers the browser can access from JavaScript.
credentialsboolean= falseAllow cookies and authorization headers in cross-origin requests.
maxAge?numberPreflight cache duration in seconds.
preflightContinueboolean= falsePass preflight requests to the next middleware instead of ending them.
optionsSuccessStatusnumber= 204HTTP status code for successful preflight responses.
privateNetworkAccessboolean= falseAdd Access-Control-Allow-Private-Network header for local/private network requests.
blockNullOriginboolean= trueBlock requests with Origin: null (from file://, data: URLs, sandboxed iframes).

Origin Types

Exact string — case-sensitive match:

cors({ origin: 'https://example.com' });

Array — whitelist of allowed origins:

cors({ origin: ['https://app.example.com', 'https://admin.example.com'] });

RegExp — pattern matching:

cors({ origin: /\.example\.com$/ });

Function — dynamic validation with async support:

cors({
  origin: async (origin, ctx) => {
    const allowed = await db.getAllowedOrigins(ctx.get('x-tenant-id'));
    return allowed.includes(origin);
  },
});

The validator receives the Origin header value and a CorsContext object. Return true to allow, false to deny, or a string to set a specific Access-Control-Allow-Origin value.

Presets

Choose a preset that matches your deployment scenario:

Allows any origin (*) without credentials. Use for public APIs or development.

import { simpleCors } from '@nextrush/cors';

app.use(simpleCors());

Requires an explicit origin. Enables credentials and blocks null origins. Caches preflight for 24 hours by default.

import { strictCors } from '@nextrush/cors';

app.use(strictCors('https://app.example.com'));

app.use(
  strictCors(['https://app.example.com', 'https://admin.example.com'], {
    exposedHeaders: ['X-Request-Id'],
  })
);

Allows localhost origins (127.0.0.1, ::1, *.localhost) with credentials and Private Network Access enabled. Null origins are allowed for file:// testing.

import { devCors } from '@nextrush/cors';

app.use(devCors());
app.use(devCors(['https://staging.example.com']));

Restricted to a list of internal service domains. Enables credentials and sets common internal headers.

import { internalCors } from '@nextrush/cors';

app.use(
  internalCors([
    'https://service1.internal.example.com',
    'https://service2.internal.example.com',
  ])
);

staticAssetsCors

Optimized for CDN and static file serving. Limits methods to GET, HEAD, OPTIONS. Caches preflight for 7 days. Defaults to '*' if no origins are specified.

import { staticAssetsCors } from '@nextrush/cors';

app.use(staticAssetsCors(['https://example.com', 'https://cdn.example.com']));

Options Builder

Build configuration with a fluent API:

import { createCorsOptions } from '@nextrush/cors';

const options = createCorsOptions()
  .allowOrigin(['https://example.com', 'https://app.example.com'])
  .allowMethods(['GET', 'POST'])
  .allowCredentials()
  .setMaxAge(86400)
  .build();

app.use(cors(options));

Integration Example

import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';

const app = createApp();

app.use(
  cors({
    origin: process.env.FRONTEND_URL,
    credentials: true,
    exposedHeaders: ['X-Request-Id'],
    maxAge: 86400,
  })
);

app.get('/api/users', (ctx) => ctx.json([{ id: 1, name: 'Alice' }]));

Common Mistakes

Wildcard with credentials

// ❌ Throws at configuration time
cors({ origin: '*', credentials: true });

// ✅ Use an explicit origin
cors({ origin: 'https://app.example.com', credentials: true });

Setting credentials: true with origin: '*' throws an error immediately. The CORS specification forbids this combination.

Wrong middleware order

// ❌ Auth rejects before CORS headers are set
app.use(authMiddleware);
app.use(cors({ origin: 'https://app.example.com' }));

// ✅ CORS runs first
app.use(cors({ origin: 'https://app.example.com' }));
app.use(authMiddleware);

Unsafe regex patterns

// ❌ ReDoS vulnerability — catastrophic backtracking
cors({ origin: /(.*)+\.example\.com/ });

// ✅ Safe pattern
cors({ origin: /\.example\.com$/ });

Troubleshooting

No CORS headers in response — Verify the request includes an Origin header and that the origin matches your configuration. With origin: false (the default), no CORS headers are set.

Preflight fails — Check that the requested method and headers are allowed. If allowedHeaders is not set, the middleware reflects the request headers automatically.

Credentials rejected — Ensure origin is set to a specific value (not '*'). The browser requires an exact origin match when credentials are included.

On this page