Skip to content

Commit

Permalink
chore: add e2e tests, handle 404 errors, add missing opts
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jan 20, 2025
1 parent a5a6fc9 commit 0ae6f54
Show file tree
Hide file tree
Showing 18 changed files with 6,899 additions and 2,461 deletions.
17 changes: 16 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
build:
working_directory: ~/nest
docker:
- image: cimg/node:21.7
- image: cimg/node:22.3
steps:
- checkout
- restore_cache:
Expand All @@ -33,8 +33,23 @@ jobs:
name: Build
command: npm run build

e2e_test:
working_directory: ~/nest
docker:
- image: cimg/node:22.3
steps:
- checkout
- *restore-cache
- *install-deps
- run:
name: E2E tests
command: npm run test:e2e

workflows:
version: 2
build-and-test:
jobs:
- build
- e2e_test:
requires:
- build
23 changes: 22 additions & 1 deletion lib/interfaces/serve-static-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export interface ServeStaticModuleOptions {
*/
rootPath?: string;
/**
* Path to render static app (concatenated with the `serveRoot` value). Default: * (wildcard - all paths). Note: `RegExp` is not supported by the `@nestjs/platform-fastify`.
* Path to render static app (concatenated with the `serveRoot` value).
* Default (express): {*any} (wildcard - all paths).
* Default (fastify): * (wildcard - all paths).
*/
renderPath?: string | RegExp;
/**
Expand Down Expand Up @@ -97,6 +99,25 @@ export interface ServeStaticModuleOptions {
* stat the stat object of the file that is being sent
*/
setHeaders?: (res: any, path: string, stat: any) => any;

/**
* Only for Fastify.
*
* When enabled, the server will attempt to send the Brotli encoded asset first,
* if supported by the Accept-Encoding headers. If Brotli is not supported,
* it will retry with gzip, and finally fall back to the original file.
*
* You may choose to skip compression for smaller files that do not benefit from it.
*/
preCompressed?: boolean;

/**
* Only for Fastify.
*
* When false, reply object won't be automatically decorated with the sendFile method.
* @default true
*/
decorateReply?: boolean;
};
}

Expand Down
31 changes: 21 additions & 10 deletions lib/loaders/express.loader.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { HttpException, Injectable, NotFoundException } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import * as fs from 'fs';
import { AbstractHttpAdapter } from '@nestjs/core';
import * as fs from 'fs';
import { ServeStaticModuleOptions } from '../interfaces/serve-static-options.interface';
import {
DEFAULT_RENDER_PATH,
DEFAULT_EXPRESS_RENDER_PATH,
DEFAULT_ROOT_PATH
} from '../serve-static.constants';
import { isRouteExcluded } from '../utils/is-route-excluded.util';
Expand All @@ -22,20 +22,22 @@ export class ExpressLoader extends AbstractLoader {
require('express')
);
optionsArr.forEach((options) => {
options.renderPath = options.renderPath || DEFAULT_RENDER_PATH;
const clientPath = options.rootPath || DEFAULT_ROOT_PATH;
options.renderPath = options.renderPath ?? DEFAULT_EXPRESS_RENDER_PATH;
const clientPath = options.rootPath ?? DEFAULT_ROOT_PATH;
const indexFilePath = this.getIndexFilePath(clientPath);

const renderFn = (req: unknown, res: any, next: Function) => {
if (!isRouteExcluded(req, options.exclude)) {
if (
options.serveStaticOptions &&
options.serveStaticOptions.setHeaders
) {
if (options.serveStaticOptions?.setHeaders) {
const stat = fs.statSync(indexFilePath);
options.serveStaticOptions.setHeaders(res, indexFilePath, stat);
}
res.sendFile(indexFilePath);
res.sendFile(indexFilePath, null, (err: Error) => {
if (err) {
const error = new NotFoundException(err.message);
res.status(error.getStatus()).send(error.getResponse());
}
});
} else {
next();
}
Expand All @@ -56,6 +58,15 @@ export class ExpressLoader extends AbstractLoader {
app.use(express.static(clientPath, options.serveStaticOptions));
app.get(options.renderPath, renderFn);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: any, _req: any, _res: any, _next: Function) => {
if (err instanceof HttpException) {
throw err;
} else if (err?.message?.includes('ENOENT') || err?.code === 'ENOENT') {
throw new NotFoundException(err.message);
}
});
});
}
}
41 changes: 22 additions & 19 deletions lib/loaders/fastify.loader.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { AbstractHttpAdapter } from '@nestjs/core';
import * as fs from 'fs';
import { ServeStaticModuleOptions } from '../interfaces/serve-static-options.interface';
import {
DEFAULT_RENDER_PATH,
DEFAULT_FASTIFY_RENDER_PATH,
DEFAULT_ROOT_PATH
} from '../serve-static.constants';
import { validatePath } from '../utils/validate-path.util';
Expand All @@ -24,11 +24,27 @@ export class FastifyLoader extends AbstractLoader {
);

optionsArr.forEach((options) => {
options.renderPath = options.renderPath || DEFAULT_RENDER_PATH;
options.renderPath = options.renderPath ?? DEFAULT_FASTIFY_RENDER_PATH;

const clientPath = options.rootPath || DEFAULT_ROOT_PATH;
const clientPath = options.rootPath ?? DEFAULT_ROOT_PATH;
const indexFilePath = this.getIndexFilePath(clientPath);

const renderFn = (req: any, res: any) => {
fs.stat(indexFilePath, (err) => {
if (err) {
const stream = fs.createReadStream(indexFilePath);
if (options.serveStaticOptions?.setHeaders) {
const stat = fs.statSync(indexFilePath);
options.serveStaticOptions.setHeaders(res, indexFilePath, stat);
}
res.type('text/html').send(stream);
} else {
const error = new NotFoundException();
res.status(error.getStatus()).send(error.getResponse());
}
});
};

if (options.serveRoot) {
app.register(fastifyStatic, {
root: clientPath,
Expand All @@ -42,27 +58,14 @@ export class FastifyLoader extends AbstractLoader {
? options.serveRoot + validatePath(options.renderPath as string)
: options.serveRoot;

app.get(renderPath, (req: any, res: any) => {
const stream = fs.createReadStream(indexFilePath);
res.type('text/html').send(stream);
});
app.get(renderPath, renderFn);
} else {
app.register(fastifyStatic, {
root: clientPath,
...(options.serveStaticOptions || {}),
wildcard: false
});
app.get(options.renderPath, (req: any, res: any) => {
const stream = fs.createReadStream(indexFilePath);
if (
options.serveStaticOptions &&
options.serveStaticOptions.setHeaders
) {
const stat = fs.statSync(indexFilePath);
options.serveStaticOptions.setHeaders(res, indexFilePath, stat);
}
res.type('text/html').send(stream);
});
app.get(options.renderPath, renderFn);
}
});
}
Expand Down
3 changes: 2 additions & 1 deletion lib/serve-static.constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const SERVE_STATIC_MODULE_OPTIONS = 'SERVE_STATIC_MODULE_OPTIONS';

export const DEFAULT_ROOT_PATH = 'client';
export const DEFAULT_RENDER_PATH = '*';
export const DEFAULT_EXPRESS_RENDER_PATH = '{*any}';
export const DEFAULT_FASTIFY_RENDER_PATH = '*';
6 changes: 3 additions & 3 deletions lib/serve-static.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { serveStaticProviders } from './serve-static.providers';
export class ServeStaticModule implements OnModuleInit {
constructor(
@Inject(SERVE_STATIC_MODULE_OPTIONS)
private readonly ngOptions: ServeStaticModuleOptions[],
private readonly moduleOptions: ServeStaticModuleOptions[],
private readonly loader: AbstractLoader,
private readonly httpAdapterHost: HttpAdapterHost
) {}
Expand Down Expand Up @@ -85,8 +85,8 @@ export class ServeStaticModule implements OnModuleInit {
};
}

public async onModuleInit() {
public onModuleInit() {
const httpAdapter = this.httpAdapterHost.httpAdapter;
this.loader.register(httpAdapter, this.ngOptions);
this.loader.register(httpAdapter, this.moduleOptions);
}
}
Loading

0 comments on commit 0ae6f54

Please sign in to comment.