URL Generation is used to dynamically generate URL that point to NestJS controller method (Route).
nestjs-url-generator can generate plain and signed URLs
npm i --save nestjs-url-generator
Or if you use Yarn:
yarn add nestjs-url-generator
nestjs-url-generator
is built to work with Nest 7 and newer versions.
First you need to import [UrlGeneratorModule]:
app.module.ts
import { UrlGeneratorModule } from 'nestjs-url-generator';
@Module({
imports: [
UrlGeneratorModule.forRoot({
secret: 'secret', // optional, required only for signed URL
appUrl: 'https://localhost:3000',
}),
],
})
export class ApplicationModule {}
Or Async Import With .ENV usage
.ENV
APP_KEY=secret
APP_URL=https://localhost:3000
signed-url.config.ts
import { UrlGeneratorModuleOptions } from 'nestjs-url-generator';
export function urlGeneratorModuleConfig(): UrlGeneratorModuleOptions {
return {
secret: process.env.APP_KEY,
appUrl: process.env.APP_URL,
};
}
app.module.ts
import { UrlGeneratorModule } from 'nestjs-url-generator';
@Module({
imports: [
ConfigModule.forRoot(),
UrlGeneratorModule.forRootAsync({
useFactory: () => urlGeneratorModuleConfig(),
}),
],
})
export class ApplicationModule {}
Now you need to register the service, by injecting it to the constructor. There are two methods for generating url:
generateUrlFromController({
controller,
controllerMethod,
/*?*/ query,
/*?*/ params,
});
generateUrlFromPath({
relativePath,
/*?*/ query,
/*?*/ params,
});
app.controller.ts
import { UrlGeneratorService } from 'nestjs-url-generator';
@Controller()
export class AppController {
constructor(private readonly urlGeneratorService: UrlGeneratorService) {}
@Get('makeUrl')
async makeUrl(): Promise<string> {
const params = {
version: '1.0',
userId: 12,
};
const query = {
email: 'email@email',
};
// This will generate:
// https://localhost:3000/emailVerification/1.0/12?email=email%40email
return this.urlGeneratorService.generateUrlFromController({
controller: AppController,
controllerMethod: AppController.prototype.emailVerification,
query: query,
params: params,
});
}
}
There are two methods for generating url:
SignControllerUrl({
controller,
controllerMethod,
/*?*/ expirationDate,
/*?*/ query,
/*?*/ params,
});
SignUrl({
relativePath,
/*?*/ expirationDate,
/*?*/ query,
/*?*/ params,
});
app.controller.ts
import { UrlGeneratorService } from 'nestjs-url-generator';
@Controller()
export class AppController {
constructor(private readonly urlGeneratorService: UrlGeneratorService) {}
@Get('makeSignUrl')
async makeSignUrl(): Promise<string> {
// This will generate:
// https://localhost:3000/emailVerification?
// expirationDate=2021-12-12T00%3A00%3A00.000Z&
// signed=84b5a021c433d0ee961932ac0ec04d5dd5ffd6f7fdb60b46083cfe474dfae3c0
return this.urlGeneratorService.SignControllerUrl({
controller: AppController,
controllerMethod: AppController.prototype.emailVerification,
expirationDate: new Date('2021-12-12'),
// or using DateTime library of your choice
// will be expired 30 minutes after it was created
expirationDate: dayjs().add(30, 'minute').toDate(),
});
}
}
[expirationDate] and [signed] query keys are used for signed URL.
By default, the signed URLs lives forever. You can add expiration date to them at the time of generating one.
The difference between params & query in ExpressJS
You can use SignUrlGuard to verify the signed url in controller.
If the url has been tampered or when the expiration date is due, then a Forbidden exception will be thrown.
app.controller.ts
import { SignedUrlGuard } from 'nestjs-url-generator';
@Controller()
export class AppController {
constructor(private readonly urlGeneratorService: UrlGeneratorService) {}
@Get('emailVerification')
@UseGuards(SignedUrlGuard)
async emailVerification(): Promise<string> {
return 'You emailed has been verified.';
}
}
Changing the secret key will invalidate all signed urls
Signed URL is typically used for unsubscribe email, email verification, sign file permission, and more.
If you are using https with reverse proxy please make sure to enable trust proxy in express
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.set('trust proxy', true);
// or
expressSession({ proxy: true });
require('crypto').randomBytes(64, (err, buf) => {
if (err) throw err;
console.log(`${buf.length} bytes of random data: ${buf.toString('base64')}`);
process.exit();
});
[ ] Create unit test (expiration, tampered, with or without globalPrefix, request with or without query & param, if target for signerUrl doesn't have guard)
[ ] Automate CI, npm run build, push, npm publish