Skip to content

Commit

Permalink
[feat/standalonenode-withmiddleware] add ability to pass middlewares …
Browse files Browse the repository at this point in the history
…when using standalone node
  • Loading branch information
Neville Mehta authored and Neville Mehta committed Jul 11, 2024
1 parent 75d118b commit 0d27ffe
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-mangos-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/node': minor
---

Add param to add middlewares when using @astrojs/node in standalone mode
3 changes: 3 additions & 0 deletions packages/integrations/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr
if (!userOptions?.mode) {
throw new AstroError(`Setting the 'mode' option is required.`);
}
if (userOptions.mode !== 'standalone' && userOptions.middleware) {
throw new AstroError(`'middleware' option is only available in 'standalone' mode.`);
}

let _options: Options;
return {
Expand Down
13 changes: 9 additions & 4 deletions packages/integrations/node/src/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const hostOptions = (host: Options['host']): string => {
return host;
};

export default function standalone(app: NodeApp, options: Options) {
export default async function standalone(app: NodeApp, options: Options) {
const port = process.env.PORT ? Number(process.env.PORT) : options.port ?? 8080;
const host = process.env.HOST ?? hostOptions(options.host);
const handler = createStandaloneHandler(app, options);
const handler = await createStandaloneHandler(app, options);
const server = createServer(handler, host, port);
server.server.listen(port, host);
if (process.env.ASTRO_NODE_LOGGING !== 'disabled') {
Expand All @@ -33,7 +33,8 @@ export default function standalone(app: NodeApp, options: Options) {
}

// also used by server entrypoint
export function createStandaloneHandler(app: NodeApp, options: Options) {
export async function createStandaloneHandler(app: NodeApp, options: Options) {
const importMiddleware = options.middleware ? await import(options.middleware) : null;
const appHandler = createAppHandler(app);
const staticHandler = createStaticHandler(app, options);
return (req: http.IncomingMessage, res: http.ServerResponse) => {
Expand All @@ -45,7 +46,11 @@ export function createStandaloneHandler(app: NodeApp, options: Options) {
res.end('Bad request.');
return;
}
staticHandler(req, res, () => appHandler(req, res));
if (importMiddleware) {
importMiddleware.default(req, res, () => staticHandler(req, res, () => appHandler(req, res)));
} else {
staticHandler(req, res, () => appHandler(req, res));
}
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/integrations/node/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { SSRManifest } from 'astro';
import type { NodeApp } from 'astro/app/node';
import type * as http from 'http';

export interface UserOptions {
/**
Expand All @@ -10,6 +11,10 @@ export interface UserOptions {
* - 'standalone' - Build to a standalone server. The server starts up just by running the built script.
*/
mode: 'middleware' | 'standalone';
/**
* Optional middleware (file path) which exports an array of middleware functions to be used in the standalone mode.
*/
middleware?: string;
}

export interface Options extends UserOptions {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

const combineMiddleware = (...middlewares) => {
return (req, res, next) => {
let index = 0;

const run = (err) => {
if (err) {
return next(err);
}
if (index >= middlewares.length) {
return next();
}
const middleware = middlewares[index++];
try {
middleware(req, res, run);
} catch (error) {
next(error);
}
};

run();
};
};

const middleware_one = (req, res, next) => {
if (req.url === '/middleware-one') {
res.end('This is middleware one');
} else {
next();
}
};

const middleware_two = (req, res, next) => {
if (req.url === '/middleware-two') {
res.end('This is middleware two');
} else {
next();
}
};

export default combineMiddleware(middleware_one, middleware_two);
78 changes: 77 additions & 1 deletion packages/integrations/node/test/node-middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('behavior from middleware, standalone', () => {
});
await fixture.build();
const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
let res = await startServer();
server = res.server;
await waitServerListen(server.server);
});
Expand All @@ -49,6 +49,65 @@ describe('behavior from middleware, standalone', () => {
});
});

describe('behavior from middleware, pass middleware to standalone', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let server;

before(async () => {
process.env.PRERENDER = false;
fixture = await loadFixture({
root: './fixtures/node-middleware/',
output: 'server',
adapter: nodejs(
{
mode: 'standalone',
middleware: `${import.meta.dirname}/fixtures/node-middleware/src/node-middleware.mjs`,
}
),
});
await fixture.build();
const { startServer } = await fixture.loadAdapterEntryModule();
let res = await startServer();
server = res.server;
await waitServerListen(server.server);
});

after(async () => {
await server.stop();
await fixture.clean();
delete process.env.PRERENDER;
});

describe('middleware-one', async () => {
it('when mode is standalone', async () => {
const res = await fetch(`http://${server.host}:${server.port}/middleware-one`);

assert.equal(res.status, 200);

const html = await res.text();
const $ = cheerio.load(html);

const body = $('body');
assert.equal(body.text().includes('This is middleware one'), true);
});
});

describe('middleware-two', async () => {
it('when mode is standalone', async () => {
const res = await fetch(`http://${server.host}:${server.port}/middleware-two`);

assert.equal(res.status, 200);

const html = await res.text();
const $ = cheerio.load(html);

const body = $('body');
assert.equal(body.text().includes('This is middleware two'), true);
});
});
});

describe('behavior from middleware, middleware', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
Expand Down Expand Up @@ -86,3 +145,20 @@ describe('behavior from middleware, middleware', () => {
assert.equal(body.text().includes("Here's a random number"), true);
});
});

describe('behavior from middleware, dont pass middleware[] option', () => {
/** @type {import('./test-utils').Fixture} */

it('when mode is standalone', async () => {
try {
await loadFixture({
root: './fixtures/node-middleware/',
output: 'server',
adapter: nodejs({ mode: 'middleware', middleware: [] }),
});
assert.fail('should have thrown an error');
} catch (err) {
assert.equal(err.message, `'middleware' option is only available in 'standalone' mode.`);
}
});
});

0 comments on commit 0d27ffe

Please sign in to comment.