1675498260
A progressive Node.js framework for building efficient and scalable server-side applications.
Nest framework TypeScript starter repository.
$ npm install
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
Author: Nestjs
Source Code: https://github.com/nestjs/typescript-starter
License: MIT licensed
1661656140
PostgreSQL module for Nest framework (node.js) π»
First install the module via yarn
or npm
and do not forget to install the driver package as well:
$ npm i --save nest-postgres pg
or
$ yarn add nest-postgres pg
PostgresModule is the primary entry point for this package and can be used synchronously
@Module({
imports: [
PostgresModule.forRoot({
connectionString: 'postgresql://[user]:[password]@[host]/[nameDb]',
// or
// host: 'localhost',
// database: [:databaseName],
// password: [:passwordDb],
// user: [:userDb],
// port: 5432,
}),
],
})
or asynchronously
@Module({
imports: [
PostgresModule.forRootAsync({
useFactory: () => ({
connectionString: 'postgresql://[user]:[password]@[host]/[nameDb]',
// or
// host: 'localhost',
// database: [:databaseName],
// password: [:passwordDb],
// user: [:userDb],
// port: 5432,
}),
}),
],
})
UsersService:
import { Client } from 'pg';
import { InjectClient } from 'nest-postgres';
@Injectable()
export class UsersService {
constructor(@InjectClient() private readonly pg: Client) {}
public async findAll(): Promise<any> {
const users = await this.pg.query('SELECT * FROM users');
return users.rows;
}
}
UsersController:
import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
async getAllUsers() {
return await this.usersService.findAll();
}
}
@Module({
imports: [
PostgresModule.forRoot(
{
connectionString: 'postgresql://postgres:pass123@localhost:5432/nest1',
// or
// host: 'localhost',
// database: [:databaseName],
// password: [:passwordDb],
// user: [:userDb],
// port: 5432,
},
'db1Connection',
),
PostgresModule.forRoot(
{
connectionString: 'postgresql://postgres:pass123@localhost:5434/nest2',
},
'db2Connection',
),
],
controllers: [],
providers: [],
})
export class AppModule {}
Usage example with Multi Connection
PostService:
import { Client } from 'pg';
import { InjectConnection } from 'nest-postgres';
import { CreatePostDto } from './dto/create-post.dto';
@Injectable()
export class PostService {
constructor(
@InjectConnection('db2Connection')
private dbConnection: Client,
) {}
public async findAll(): Promise<any> {
const users = await this.dbConnection.query('SELECT * FROM posts');
return users.rows;
}
public async create(createPostDto: CreatePostDto): Promise<any> {
try {
const user = await this.dbConnection.query(
'INSERT INTO posts (title, description) VALUES ($1, $2) RETURNING *',
[createPostDto.title, createPostDto.description],
);
return user.rows;
} catch (err) {
throw new HttpException(err, HttpStatus.BAD_REQUEST);
}
}
}
UsersService:
import { Client } from 'pg';
import { InjectConnection } from 'nest-postgres';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectConnection('db1Connection')
private dbConnection: Client,
) {}
public async findAll(): Promise<any> {
const users = await this.dbConnection.query('SELECT * FROM users');
return users.rows;
}
public async create(createUserDto: CreateUserDto): Promise<any> {
try {
const user = await this.dbConnection.query(
'INSERT INTO users (firstName, lastName) VALUES ($1, $2) RETURNING *',
[createUserDto.firstName, createUserDto.lastName],
);
return user.rows;
} catch (err) {
throw new HttpException(err, HttpStatus.BAD_REQUEST);
}
}
}
For more information on node-postgres
for Nodejs see here
Feel free to help this library, I'm quite busy with also another Nestjs packages, but the community will appreciate the effort of improving this library. Make sure you follow the guidelines
Author: Tony133
Source code: https://github.com/Tony133/nestjs-postgres
License: MIT license
#nest #nestjs #node #typescript #postgressql
1661641380
Provide log4js module as NestJS module
This is root of @nestx-log4js
monorepo.
To Get Started, please read @nestx-log4js/core
Documentation
log4js
as NestJS Module.
log4js
wrapper as NestJS Global ModuleLog4jsLogger
instance for replacement logger usageyarn add @nestx-log4js/core
log4js
?Since logger is a special service in NestJS, we suppose import
Log4jsModule
and manual callapp.useLogger(app.get(Log4jsLogger))
app.module.ts
import { Module } from '@nestjs/common';
import { Log4jsModule } from '@nestx-log4js/core';
@Module({
imports: [
Log4jsModule.forRoot()
]
})
export class AppModule {}
bootstrap.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Log4jsLogger } from '@nestx-log4js/core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(app.get(Log4jsLogger));
await app.listen(3000);
}
bootstrap();
For more details, you can refer unit tests in source code
Log4jsModule
with AsyncOptions (Production Usage)You might want to use different appender (e.g. file/dateFile appender) in real production usage and initial in your ConfigService/LoggerOnlyConfigService
app.module.ts
import { Module } from '@nestjs/common';
import { Log4jsModule } from '@nestx-log4js/core';
// import your ConfigService/LoggerConfigService
@Module({
imports: [
Log4js.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => config.getLog4jsOptions() // config.getLog4jsOptions should return valid Log4jsOptions
})
]
})
export class AppModule {}
When using Log4jsModule.forRoot()
and no spec any appenders and layouts,
It will use default below layouts:
export const LOG4JS_DEFAULT_LAYOUT = {
type: 'pattern',
// log4js default pattern %d{yyyy-MM-dd HH:mm:ss:SSS} [%thread] %-5level %logger{36} - %msg%n
// we use process id instead thread id
pattern: '%[%d{yyyy-MM-dd hh:mm:ss:SSS} %p --- [%15.15x{name}]%] %40.40f{3} : %m',
tokens: {
name: (logEvent) => {
return (logEvent.context && logEvent.context['name']) || '-';
}
}
};
export const LOG4JS_NO_COLOUR_DEFAULT_LAYOUT = {
type: 'pattern',
// log4js default pattern %d{yyyy-MM-dd HH:mm:ss:SSS} [%thread] %-5level %logger{36} - %msg%n
// we use process id instead thread id
pattern: '%d{yyyy-MM-dd hh:mm:ss:SSS} %p --- [%15.15x{name}] %40.40f{3} : %m',
tokens: {
name: (logEvent) => {
return (logEvent.context && logEvent.context['name']) || '-';
}
}
};
export const LOG4JS_DEFAULT_CONFIG: Configuration = {
appenders: {
stdout: {
type: 'stdout',
layout: LOG4JS_DEFAULT_LAYOUT
},
file: {
type: 'file',
filename: './logs/application.log',
maxLogSize: 20 * 1024 * 1024, // maxLogSize use bytes ad unit
backups: 10, // default use 5 so 1KB file size total rotating
layout: LOG4JS_NO_COLOUR_DEFAULT_LAYOUT
}
},
categories: {
default: {
enableCallStack: true,
appenders: ['stdout', 'file'],
level: 'debug'
}
}
};
It will use default below layouts (from spring boot default log pattern without process id)
You can refer to SpringBoot logging features
2020-11-14 15:47:24:486 INFO --- [ NestJS] core/src/log4js.classes.ts : log using nestjs as category
2020-11-14 15:47:24:486 INFO --- [ -] core/src/log4js.classes.ts : log using none as category
2020-11-14 15:47:24:490 INFO --- [ NestJS] core/src/log4js.classes.ts : log using nestjs as category
2020-11-14 15:47:24:490 WARN --- [ -] src/__tests__/log4js.module.test.ts : log using none as category
Tips: You are using grok pattern via filebeat/other sidecar log agent? You can use below grok pattern: %{TIMESTAMP_ISO8601:server_time}\s*%{LOGLEVEL:level}\s*---\s[\s%{NOTSPACE:context}]\s*%{NOTSPACE:file_path}\s*:\s*%{GREEDYDATA:content}
It will split to friendly format (you can update alias yourself via grok document)
- server_time
- level
- context
- file_path
- content
Author: nest-x
Source code: https://github.com/nest-x/nestx-log4js
License: MIT license
#nest #nestjs #node #typescript
1661626860
This package makes it easy to send Twilio notifications with NestJS.
This package is a sub-module of NestJS notification.
::: code
$ npm i @nestjs-notification-channels/twilio
$ yarn add @nestjs-notification-channels/twilio
:::
Add your Twilio Account SID, Auth Token, and Sender (optional) to your .env
:
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_SENDER=
Module declaration
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {
NestjsNotificationModule,
NestjsNotificationModuleOptions,
} from '@sinuos/nestjs-notification';
@Module({
imports: [
NestjsNotificationModule.register(<NestjsNotificationModuleOptions>{}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
You can also declare the account sid, auth token and sender by declaring them like this.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {
NestjsNotificationModule,
NestjsNotificationModuleOptions,
} from '@sinuos/nestjs-notification';
import { TwilioChannelModule } from '@nestjs-notification-channels/twilio';
@Module({
imports: [
NestjsNotificationModule.register(<NestjsNotificationModuleOptions>{}),
TwilioChannelModule.register({
twilioAccountSid: 'xxx',
twilioAuthToken: 'xxx',
twilioSender: 'xxx', // SENDER OR +212xxxxxx,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Now you can use the channel in your sendToChannels()
method inside the notification:
import { NestJsNotification } from '@sinuos/nestjs-notification';
import {
TwilioChannel,
TwilioChannelMessage,
ITwilioChannel,
} from '@nestjs-notification-channels/twilio';
export class OrderedNotification implements NestJsNotification {
public sendToChannels() {
return [TwilioChannel];
}
toTwilio() {
return new TwilioChannelMessage()
.setToFrom('+212xxxxxxxx')
.setMessage('Your order XXXXX is placed');
}
}
accountSid('')
- Optional if account sid is defined in your env.authToken('')
- Optional if auth token is defined in your env.sender('')
- Optional if sender is defined in your env.message('')
- SMS content.toPhoneNumber('')
- Your recipient.Please see CHANGELOG for more information what has changed recently.
::: code
$ npm run test
$ yarn test
:::
If you discover any security related issues, please email gregoriohc@gmail.com instead of using the issue tracker.
Please see CONTRIBUTING for details.
Author: nestjs-notification-channels
Source code: https://github.com/nestjs-notification-channels/twilio
License: MIT license
#nest #nestjs #node #typescript
1661622900
A split node backend and electron main process
Doubleshot is a collection of tools for integrating the nodejs backend framework with electron to build a desktop application. At some point in the future, you can simply and quickly split it up and convert it to a web application.
A software engineer's time is precious, and I hope that one set of code can be applied to two different applications with minor modifications. So we can have more time to enjoy a cup of coffee. π
Warning: this project is in early stage, do not use in production environment
Author: Doubleshotjs
Source code: https://github.com/Doubleshotjs/doubleshot
License: MIT license
#nest #nestjs #node
1661615340
NestJS Zeebe Connector (Transport and Client) - Up-to-date
A zeebe transport and client for NestJS 7.x
Using the zeebe-node module and exposing it as a NestJS transport and module.
Use version 2.x.x and above for Zeebe 1.x.x and above
npm install nestjs-zeebe
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ZeebeModule, ZeebeServer } from 'nestjs-zeebe';
@Module({
imports: [ ZeebeModule.forRoot({ gatewayAddress: 'localhost:26500' })],
controllers: [AppController],
providers: [ZeebeServer],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ZeebeServer } from 'nestjs-zeebe';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const microservice = app.connectMicroservice({
strategy: app.get(ZeebeServer),
});
await app.startAllMicroservicesAsync();
await app.listen(3000);
}
bootstrap();
// app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { ZBClient } from 'zeebe-node';
import { ZEEBE_CONNECTION_PROVIDER, ZeebeWorker, ZeebeJob } from 'nestjs-zeebe';
import {
Ctx,
Payload,
} from '@nestjs/microservices';
import { ZBClient, ZBWorker, ICustomHeaders, IInputVariables, IOutputVariables, CompleteFn } from 'zeebe-node';
import { CreateProcessInstanceResponse } from 'zeebe-node/dist/lib/interfaces-grpc-1.0';
@Controller()
export class AppController {
constructor(private readonly appService: AppService, @Inject(ZEEBE_CONNECTION_PROVIDER) private readonly zbClient: ZBClient) {}
// Use the client to create a new workflow instance
@Get()
getHello() : Promise<CreateProcessInstanceResponse> {
return this.zbClient.createProcessInstance('order-process', { test: 1, or: 'romano'});
}
// Subscribe to events of type 'payment-service
@ZeebeWorker('payment-service')
paymentService(@Payload() job: ZeebeJob, @Ctx() context: { complete: CompleteFn<IOutputVariables>, worker: ZBWorker<IInputVariables, ICustomHeaders, IOutputVariables> }) {
console.log('Payment-service, Task variables', job.variables);
let updatedVariables = Object.assign({}, job.variables, {
paymentService: 'Did my job',
});
// Task worker business logic goes here
job.complete(updatedVariables);
}
// Subscribe to events of type 'inventory-service and create a worker with the options as passed below (zeebe-node ZBWorkerOptions)
@ZeebeWorker('inventory-service', { maxJobsToActivate: 10, timeout: 300 })
inventoryService(@Payload() job: ZeebeJob, @Ctx() context: { complete: CompleteFn<IOutputVariables>, worker: ZBWorker<IInputVariables, ICustomHeaders, IOutputVariables> }) {
console.log('inventory-service, Task variables', job.variables);
let updatedVariables = Object.assign({}, job.variables, {
inventoryVar: 'Inventory donnnneee',
});
// Task worker business logic goes here
//job.complete(updatedVariables);
job.complete(updatedVariables);
}
}
// app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { ZBClient } from 'zeebe-node';
import { CreateWorkflowInstanceResponse, CompleteFn, Job } from 'zeebe-node/interfaces';
import { ZEEBE_CONNECTION_PROVIDER, ZeebeWorker } from 'nestjs-zeebe';
import {
Ctx,
Payload,
} from '@nestjs/microservices';
@Controller()
export class AppController {
constructor(private readonly appService: AppService, @Inject(ZEEBE_CONNECTION_PROVIDER) private readonly zbClient: ZBClient) {}
// Use the client to create a new workflow instance
@Get()
getHello() : Promise<CreateWorkflowInstanceResponse> {
return this.zbClient.createWorkflowInstance('order-process', { test: 1, or: 'romano'});
}
// Subscribe to events of type 'payment-service
@ZeebeWorker('payment-service')
paymentService(@Payload() job, @Ctx() fn: CompleteFn<any> {
console.log('Payment-service, Task variables', job.variables);
let updatedVariables = Object.assign({}, job.variables, {
paymentService: 'Did my job',
});
// Task worker business logic goes here
complete.success(updatedVariables);
}
// Subscribe to events of type 'inventory-service and create a worker with the options as passed below (zeebe-node ZBWorkerOptions)
@ZeebeWorker('inventory-service', { maxJobsToActivate: 10, timeout: 300 })
inventoryService(@Payload() job, @Ctx() fn: CompleteFn<any>) {
console.log('inventory-service, Task variables', job.variables);
let updatedVariables = Object.assign({}, job.variables, {
inventoryVar: 'Inventory donnnneee',
});
// Task worker business logic goes here
complete.success(updatedVariables);
}
}
Author: camunda-community-hub
Source code: https://github.com/camunda-community-hub/nestjs-zeebe
License: MIT license
#nest #nestjs #node #typescript
1661607902
This is an official AdminJS plugin which integrates it to nestjs framework.
Version >3.0.0 might not be compatible with Nestjs 7 or lower. If adapter is not working, try downgrading adapter to version 2..
AdminJS is an automatic admin interface which can be plugged into your application. You, as a developer, provide database models (like posts, comments, stores, products or whatever else your application uses), and AdminJS generates UI which allows you (or other trusted users) to manage content.
Check out the example application with mongo and postgres models here: https://admin-bro-example-app-staging.herokuapp.com/admin/login
Or visit AdminJS github page.
To see example usage see the example-app or visit the Nestjs section under AdminJS project page
Weβre an open, friendly team that helps clients from all over the world to transform their businesses and create astonishing products.
Author: SoftwareBrothers
Source code: https://github.com/SoftwareBrothers/adminjs-nestjs
License: MIT license
#nest #nestjs #node
1661600468
Injectable Stripe client for your nestjs projects
nestjs-stripe
implements a module, StripeModule
, which when imported into your nestjs project provides a Stripe client to any class that injects it. This lets Stripe be worked into your dependency injection workflow without having to do any extra work outside of the initial setup.
npm install --save nestjs-stripe
The simplest way to use nestjs-stripe
is to use StripeModule.forRoot
import { Module } from '@nestjs-common';
import { StripeModule } from 'nestjs-stripe';
@Module({
imports: [
StripeModule.forRoot({
apiKey: 'my_secret_key',
apiVersion: '2020-08-27',
}),
],
})
export class AppModule {}
You can then inject the Stripe client into any of your injectables by using a custom decorator
import { Injectable } from '@nestjs/common';
import { InjectStripe } from 'nestjs-stripe';
import Stripe from 'stripe';
@Injectable()
export class AppService {
public constructor(@InjectStripe() private readonly stripeClient: Stripe) {}
}
Asynchronous setup is also supported
import { Module } from '@nestjs-common';
import { StripeModule } from 'nestjs-stripe';
@Module({
imports: [
StripeModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
apiKey: configService.get('stripe_key'),
apiVersion: '2020-08-27',
}),
}),
],
})
export class AppModule {}
Read up on the stripe-node
caveats here. Due to the way stripe-node
works you can only use the latest version of the Stripe API that was published at the time the module version was published. If you wish to use an older version of the Stripe API, follow the steps in the above link. Because of this, the apiVersion
field is now required along with the apiKey
field.
In order to run the example run the following commands in your terminal. The expected output of the example is to show that the Stripe client was successfully injected into the AppService
.
cd example
yarn install
yarn start
I would greatly appreciate any contributions to make this project better. Please make sure to follow the below guidelines before getting your hands dirty.
git checkout -b my-branch
)Author: dhaspden
Source code: https://github.com/dhaspden/nestjs-stripe
License: MIT license
#nest #nestjs #node #stripe
1661593080
A module for Braintree reoccurring payments and transactions built for the Nestjs framework.
Using the Braintree node SDK.
NOTE! Currently building
$ yarn add nestjs-braintree
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
import * as braintree from 'braintree';
@Module({
imports: [
BraintreeModule.forRoot({
environment: braintree.Environment.Sandbox,
merchantId: '',
publicKey: '',
privateKey: '',
}),
],
})
export default class AppModule {}
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
@Module({
imports: [
BraintreeModule.forFeature(),
],
})
export default class SubModule {}
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
],
})
export default class AppModule {}
//config/braintree.ts
import * as braintree from 'braintree';
export default {
environment:
process.env.NODE_ENV == 'development'
? braintree.Environment.Sandbox
: braintree.Environment.Live,
merchantId: process.env.BRAINTREE_MERCHANT_ID,
publicKey: process.env.BRAINTREE_PUBLIC_KEY,
privateKey: process.env.BRAINTREE_PRIVATE_KEY,
};
Braintree is capable of making one off transactions
import { Module } from '@nestjs/common';
import { BraintreeModule, InjectBraintreeProvider } from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
class TransactionProvider {
constructor(
@InjectBraintreeProvider()
private readonly braintreeProvider: BraintreeProvider,
) {}
takePayment(amount: string, nonce: string) {
this.braintreeProvider.sale({
payment_method_nonce: nonce,
amount,
});
}
}
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRoot({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
],
providers: [TransactionProvider],
})
export default class AppModule {}
Available methods relating to transactions are
braintreeProvider.sale(transaction: BraintreeTransactionInterface): Promise<BraintreeTransactionResultInterface>
braintreeProvider.refund(transactionId: string, amount?: string, orderId?: string): Promise<BraintreeTransactionResultInterface>
braintreeProvider.find(transactionId: string): Promise<BraintreeTransactionResultInterface>
The braintree SDK does offer additional methods. I will implement them soon hopefully
When using subscriptions with braintree, braintree will issue webhooks to your endpoint which you can use the decorators to handle those actions.
import { Module } from '@nestjs/common';
import {
BraintreeModule,
BraintreeWebhookModule,
BraintreeSubscriptionCanceled,
BraintreeSubscriptionExpired,
BraintreeWebhookHandler,
} from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
@BraintreeWebhookHandler()
class SubscriptionProvider {
@BraintreeSubscriptionCanceled()
canceled() {
console.log('subscription canceled');
}
@BraintreeSubscriptionExpired()
expired() {
console.log('subscription expired');
}
}
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
BraintreeWebhookModule,
],
providers: [SubscriptionProvider],
})
export default class AppModule {}
The idea of the Braintree Webhook Module is to make implementation of actions a lot easier. For example we can build a provider like this one to cancel canceled subscriptions.
@BraintreeWebhookHandler()
export class SubscriptionProvider {
constructor(@InjectRepository(Subscription) private readonly subscriptionRepository: Repository<Subscription>) {}
async findByBraintreeId(braintreeId: string): Promise<Subscription|null> {
return await this.subscriptionRepository.find({
where: {
braintreeId,
},
});
}
async update(subscription: Subscription): Promise<boolean> {
return await this.subscriptionRepository.update(subscription);
}
@BraintreeSubscriptionCanceled()
async canceled(webhook: BraintreeWebhook) {
const subscription = await this.findByBraintreeId(webhook.subscription.id);
if (!subscription) {
return;
}
subscription.active = false;
await this.update(subscription);
}
}
Shortname | Braintree webhook name/const/key | NestJS decorator |
---|---|---|
Subscription Canceled | subscription_canceled | @BraintreeSubscriptionCanceled() |
Subscription Expired | subscription_expired | @BraintreeSubscriptionExpired() |
Subscription Charged Successfully | subscription_charged_successfully | @BraintreeSubscriptionChargedSuccessfully() |
Subscription Charged Unsuccessfully | subscription_charged_unsuccessfully | @BraintreeSubscriptionChargedUnsuccessfully() |
Subscription Went Active | subscription_went_active | @BraintreeSubscriptionWentActive() |
Subscription Went Past Due | subscription_went_past_due | @BraintreeSubscriptionWentPastDue() |
Subscription Trial Ended | subscription_trial_ended | @BraintreeSubscriptionTrialEnded() |
You can find out more about the webhooks here.
You may want to divert from the default routing of {your_domain}/braintree/webhook
for whatever reason. You can do so using the forRoot
method on the BraintreeWebhookModule
like so
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
BraintreeWebhookModule.forRoot({
root: 'replace-braintree',
handle: 'replace-webhook',
}),
],
providers: [SubscriptionProvider],
})
export default class AppModule {}
The above will result in your route for your braintree webhooks being {your_domain}/replace-braintree/replace-webhook
Author: nestjsx
Source code: https://github.com/nestjsx/nestjs-braintree
License: MIT license
#nest #nestjs #node
1661585642
Pagination helper method for TypeORM repositories or queryBuilders with strict typings
$ yarn add nestjs-typeorm-paginate
or
$ npm i nestjs-typeorm-paginate
If you're using typeorm^0.2.6 please use nestjs-typeorm-paginate^3.2.0 For typeorm^0.3.0 please use nestjs-typeorm-paginate^4.0.0
Service
Repository
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
paginate,
Pagination,
IPaginationOptions,
} from 'nestjs-typeorm-paginate';
@Injectable()
export class CatService {
constructor(
@InjectRepository(CatEntity)
private readonly repository: Repository<CatEntity>,
) {}
async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
return paginate<CatEntity>(this.repository, options);
}
}
QueryBuilder
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
paginate,
Pagination,
IPaginationOptions,
} from 'nestjs-typeorm-paginate';
@Injectable()
export class CatService {
constructor(
@InjectRepository(CatEntity)
private readonly repository: Repository<CatEntity>,
) {}
async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
const queryBuilder = this.repository.createQueryBuilder('c');
queryBuilder.orderBy('c.name', 'DESC'); // Or whatever you need to do
return paginate<CatEntity>(queryBuilder, options);
}
}
Controller
import { Controller, DefaultValuePipe, Get, ParseIntPipe, Query } from '@nestjs/common';
import { CatService } from './cat.service';
import { CatEntity } from './cat.entity';
import { Pagination } from 'nestjs-typeorm-paginate';
@Controller('cats')
export class CatsController {
constructor(private readonly catService: CatService) {}
@Get('')
async index(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10,
): Promise<Pagination<CatEntity>> {
limit = limit > 100 ? 100 : limit;
return this.catService.paginate({
page,
limit,
route: 'http://cats.com/cats',
});
}
}
If you use
ParseIntPipe
on the query params (as in the example), don't forget to also addDefaultValuePipe
. See issue 517 for more info.
the
route
property of the paginate options can also be the short version of an absolute path , In this case, it would be/cats
instead ofhttp://cats.com/cats
{
"items": [
{
"lives": 9,
"type": "tabby",
"name": "Bobby"
},
{
"lives": 2,
"type": "Ginger",
"name": "Garfield"
},
{
"lives": 6,
"type": "Black",
"name": "Witch's mate"
},
{
"lives": 7,
"type": "Purssian Grey",
"name": "Alisdaya"
},
{
"lives": 1,
"type": "Alistair",
"name": "ali"
},
...
],
"meta": {
"itemCount": 10,
"totalItems": 20,
"itemsPerPage": 10,
"totalPages": 5,
"currentPage": 2
},
"links" : {
"first": "http://cats.com/cats?limit=10",
"previous": "http://cats.com/cats?page=1&limit=10",
"next": "http://cats.com/cats?page=3&limit=10",
"last": "http://cats.com/cats?page=5&limit=10"
}
}
items
: An array of SomeEntity
meta.itemCount
: The length of items array (i.e., the amount of items on this page) meta.totalItems
: The total amount of SomeEntity matching the filter conditions meta.itemsPerPage
: The requested items per page (i.e., the limit
parameter)
meta.totalPages
: The total amount of pages (based on the limit
) meta.currentPage
: The current page this paginator "points" to
links.first
: A URL for the first page to call | ""
(blank) if no route
is defined links.previous
: A URL for the previous page to call | ""
(blank) if no previous to call links.next
: A URL for the next page to call | ""
(blank) if no page to call links.last
: A URL for the last page to call | ""
(blank) if no route
is defined
Do note that
links.first
may not have the 'page' query param defined
@Injectable()
export class CatService {
constructor(
@InjectRepository(CatEntity)
private readonly repository: Repository<CatEntity>,
) {}
async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
return paginate<CatEntity>(this.repository, options, {
lives: 9,
});
}
}
Eager loading should work with typeorm's eager property out the box. Like so
import { Entity, OneToMany } from 'typeorm';
@Entity()
export class CatEntity {
@OneToMany(t => TigerKingEntity, tigerKing.cats, {
eager: true,
})
tigerKings: TigerKingEntity[];
}
// service
class CatService {
constructor(private readonly repository: Repository<CatEntity>) {}
async paginate(page: number, limit: number): Promise<Pagination<CatEntity>> {
return paginate(this.repository, { page, limit });
}
}
However, when using the query builder you'll have to hydrate the entities yourself. Here is a crude example that I've used in the past. It's not great but this is partially what typeORM will do.
const results = paginate(queryBuilder, { page, limit });
return new Pagination(
await Promise.all(
results.items.map(async (item: SomeEntity) => {
const hydrate = await this.someRepository.findByEntity(item);
item.hydrated = hydrate;
return item;
}),
),
results.meta,
results.links,
);
const queryBuilder = this.repository
.createQueryBuilder<{ type: string; totalLives: string }>('c')
.select('c.type', 'type')
.addSelect('SUM(c.lives)', 'totalLives')
.groupBy('c.type')
.orderBy('c.type', 'DESC'); // Or whatever you need to do
return paginateRaw(queryBuilder, options);
A similar approach is used for TypeORM's getRawAndEntities
Let's assume there's a joined table that matches each cat with its cat toys. And we want to bring how many toys each cat has.
const queryBuilder = this.repository
.createQueryBuilder<{ type: string; totalLives: string }>('cat')
.leftJoinAndSelect('cat.toys', 'toys')
.addSelect('COUNT(toys)::INTEGER', 'toyCount')
.groupBy('cat.name');
This will allow us to get the paginated cats information with the additional raw query to build our actual response value. The return pagination object will be the same, but you're now able to handle or map the results and the raw objects as needed.
const [pagination, rawResults] = await paginateRawAndEntities(query, options);
pagination.items.map((item, index) => {
// we can do what we need with the items and raw results here
// change your items using rawResults.find(raw => raw.id === item.id)
});
return pagination;
Since the values of the raw results will include all the joined table items as queried, you must make sure to handle the items as needed for your use case. Refer to TypeORM's getRawAndEntities implementation as needed.
The rawResults array will look something like this:
[
{ // Bobby appears 3 times due to the joined query
"cat_lives": 9,
"cat_type": "tabby",
"cat_name": "Bobby",
"toyCount": 3
},
{
"cat_lives": 9,
"cat_type": "tabby",
"cat_name": "Bobby",
"toyCount": 3
},
{
"cat_lives": 9,
"cat_type": "tabby",
"cat_name": "Bobby",
"toyCount": 3
},
{
"cat_lives": 2,
"cat_type": "Ginger",
"cat_name": "Garfield",
"toyCount": 1
},
...
]
If you wanted to alter the meta data that is returned from the pagination object. Then use the metaTransformer
in the options like so
class CustomPaginationMeta {
constructor(
public readonly count: number,
public readonly total: number,
) {}
}
return paginate<MyEntity, CustomPaginationMeta>(this.repository, {
page,
limit,
metaTransformer: (meta: IPaginationMeta): CustomPaginationMeta => new CustomPaginationMeta(
meta.itemCount,
meta.totalItems,
),
});
This will result in the above returning CustomPaginationMeta
in the meta
property instead of the default IPaginationMeta
.
If you want to alter the limit
and/or page
labels in meta links, then use routingLabels
in the options like so
return paginate<MyEntity>(this.repository, {
page,
limit,
routingLabels: {
limitLabel: 'page-size', // default: limit
pageLabel: 'current-page', //default: page
}
});
This will result links like http://example.com/something?current-page=1&page-size=3
.
Author: nestjsx
Source code: https://github.com/nestjsx/nestjs-typeorm-paginate
License: MIT license
#nest #nestjs #node #typeorm
1661578080
@golevelup/nestjs-hasura
Leverage NestJS to make incorporating business logic and event processing easier with Hasura
π Exposes an API endpoint from your NestJS application at to be used for event processing from Hasura. Defaults to /hasura/events/
but can be easily configured
π Automatically validates that the event payload was actually sent from Hasura using configurable secrets
π΅οΈ Discovers methods from your application and automatically turns them into Hasura event handlers. Supports insert, update and delete events from your tables as well as scheduled events based on a CRON schedule
π§ Routes incoming webhook payloads to the correct event handler based on configuration so you can maintain a single webhook endpoint for Hasura
π Optionally supports automatic management of your Hasura metadata files which means that your application code can be the source of truth for configuration of events. This reduces a ton of boilerplate and developer overhead
npm install ---save @golevelup/nestjs-hasura
or
yarn add @golevelup/nestjs-hasura
Import and add HasuraModule
to the imports
section of the consuming module (most likely AppModule
). In order to ensure that your Hasura events webhook endpoint is secure, the module requires configuration for an HTTP header name and value that will be used to verify that the event actually came from Hasura.
The Hasura Module supports both the forRoot
and forRootAsync
patterns for configuration, so you can easily retrieve the necessary config values from a ConfigService
or other provider.
The HasuraModule
makes it easy to reuse the same events API endpoint for all events that you create in Hasura. The internal routing mechanism on the NestJS side ensures that the all events coming in through the endpoint will be sent to the correct handler. The endpoint provided defaults to /hasura/events
. This can be overriden with the module by specifying an alternative controllerPrefix
so for example you could set this to webhooks
and the resulting endpoint would be available at /webhooks/events
.
One of the more powerful features of this Module is the ability to automatically generate the necessary Hasura metadata for your event handlers instead of having to worry about configuring each handler individually. Under the hood, this uses the @hasura/metadata
to generate and merge changes to your tables.yaml
and cron_triggers.yaml
files.
If you decide to opt into this functionality, you should include the optional managedMetaDataConfig
object when importing the HasuraModule into your application.
import { HasuraModule } from '@golevelup/nestjs-hasura';
@Module({
imports: [
HasuraModule.forRoot(HasuraModule, {
webhookConfig: {
secretFactory: secret,
secretHeader: secretHeader,
},
managedMetaDataConfig: {
dirPath: join(process.cwd(), 'hasura/metadata'),
secretHeaderEnvName: 'HASURA_NESTJS_WEBHOOK_SECRET_HEADER_VALUE',
nestEndpointEnvName: 'NESTJS_EVENT_WEBHOOK_ENDPOINT',
defaultEventRetryConfig: {
intervalInSeconds: 15,
numRetries: 3,
timeoutInSeconds: 100,
toleranceSeconds: 21600,
},
},
}),
],
})
export class AppModule {
// ...
}
It is recommended that you conditionally add this configuration based on the Node Environment as this should only be used in development environments to track the necessary changes to your metadata yaml files so that they can be tracked in source control.
After generating changes to these files you should make sure they are applied against your Hasura instance using the CLI command:
hasura metadata apply
If you decide to opt out of automatic metadata synchronization it is up to you to ensure that the secret header name and values match. When creating the event in the Hasura console, you should set these values such that they match the configuration provided to the HasuraModule
configuration in your NestJS application. This ensures that only Hasura can trigger events in your system.
Decorate methods in your NestJS providers in order to have them be automatically attached as event handlers for incoming Hasura events. The event payload will be analyzed and routed to your provider methods based on the configuration provided in the decorator.
import {
TrackedHasuraEventHandler,
HasuraUpdateEvent,
HasuraInsertEvent,
} from '@golevelup/nestjs-hasura';
@Injectable()
class UsersService {
@TrackedHasuraEventHandler({
triggerName: 'user-created',
tableName: 'user',
definition: { type: 'insert' },
})
handleUserCreated(evt: HasuraInsertEvent<User>) {}
@TrackedHasuraEventHandler({
triggerName: 'user-updated',
tableName: 'user',
definition: { type: 'update', columns: ['avatarUrl'] },
})
handleUserUpdated(evt: HasuraUpdateEvent<User>) {}
}
import { TrackedHasuraScheduledEventHandler } from '@golevelup/nestjs-hasura';
@Injectable()
class RecurringJobService {
@TrackedHasuraScheduledEventHandler({
cronSchedule: CommonCronSchedules.EveryMinute,
name: 'every-minute',
payload: {},
comment: 'this is my comment',
})
public async cronTask(evt: any) {
this.logger.log(evt);
}
}
Retry configuration for both Table Event handlers as well as Scheduled Event handlers can be configured on the individual decorator or you can provide a default retry configuration at the module level that will be used for any event handler that does not explicitly provide its own retry settings.
You should provide ENV variables to your Hasura instance that map the webhook endpoint and secret header values for communication to your NestJS application.
In the examples above, HASURA_NESTJS_WEBHOOK_SECRET_HEADER_VALUE
and NESTJS_EVENT_WEBHOOK_ENDPOINT
were used. The webhook endpoint should point to the automatically scaffolded events endpoint eg: https://my-nest-app.com/api/hasura/events
This library is built using an underlying NestJS concept called External Contexts
which allows for methods to be included in the NestJS lifecycle. This means that Guards, Interceptors and Filters (collectively known as "enhancers") can be used in conjunction with Hasura event handlers. However, this can have unwanted/unintended consequences if you are using Global enhancers in your application as these will also apply to all Hasura event handlers. If you were previously expecting all contexts to be regular HTTP contexts, you may need to add conditional logic to prevent your enhancers from applying to Hasura event handlers.
You can identify Hasura event contexts by their context type, 'hasura_event'
:
@Injectable()
class ExampleInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>) {
const contextType = context.getType<'http' | 'hasura_event'>();
// Do nothing if this is a Hasura event
if (contextType === 'hasura_event') {
return next.handle();
}
// Execute custom interceptor logic for HTTP request/response
return next.handle();
}
}
https://hasura.io/docs/1.0/graphql/manual/event-triggers/index.html#event-triggers
https://hasura.io/docs/1.0/graphql/manual/getting-started/first-event-trigger.html https://hasura.io/event-triggers
Contributions welcome! Read the contribution guidelines first.
Link: https://github.com/golevelup/nestjs/tree/master/packages/hasura
Link: https://github.com/golevelup/nestjs/tree/master/packages/hasura
#nest #nestjs #node #graphql #hasura
1661570352
This library provides the GraphQLRequestModule
which makes it easy to initialize an instance of GraphQLClient
for use in Depdendency Injection. This can be combined with automatic codegen for type safe access to third party GQL APIs from your Nest services
npm install ---save @golevelup/nestjs-graphql-request
or
yarn add @golevelup/nestjs-graphql-request
Import and add GraphQLRequestModule
to the imports
section of the consuming module.
import { GraphQLRequestModule } from '@golevelup/nestjs-graphql-request';
@Module({
imports: [
GraphQLRequestModule.forRoot(GraphQLRequestModule, {
// Exposes configuration options based on the graphql-request package
endpoint: config.get('endpoint'),
options: {
headers: {
'content-type': 'application/json',
'x-hasura-admin-secret': config.get('secret'),
},
},
}),
],
})
export class AppModule {
// ...
}
To make GraphQL requests from your controllers or services use the provided decorator @InjectGraphQLClient
.
import { InjectGraphQLClient } from '@golevelup/nestjs-graphql-request';
import { GraphQLClient } from 'graphql-request';
@Injectable()
export class ExampleService {
constructor(@InjectGraphQLClient() private readonly client: GraphQLClient) {}
}
The GraphQL client works best when combined with GraphQL code generation tools for communicating to a GraphQL endpoint. Follow the GraphQL Code Generator GraphQL Request Plugin Instructions.
Running the code generator against a GraphQL Endpoint will produce typescript interfaces for all operations and types exposed by the API as well as a getSdk
function which you can use in conjunction with GraphQL Request to get intellisense and type saftey for all requests you make. The getSdk
function only requires a GraphQLClient instance (which this library provides). However because each generated SDK is unique per endpoint, it's up to you to wire up the provider for this in your application. This can be done using a Provider Factory with minimal code.
import {
GraphQLRequestModule,
GraphQLClientInject,
} from '@golevelup/nestjs-graphql-request';
import { getSdk } from './your-codegen-location';
@Module({
imports: [
GraphQLRequestModule.forRoot(GraphQLRequestModule, {
// Exposes configuration options based on the graphql-request package
endpoint: config.get('endpoint'),
options: {
headers: {
'content-type': 'application/json',
'x-hasura-admin-secret': config.get('secret'),
},
},
}),
],
providers: [
{
// you can provide whatever key you want. use it in conjunction with @Inject("TypeSafeGqlSdk") to get the SDK instance in your controllers/services
provide: 'TypeSafeGqlSdk',
inject: [GraphQLClientInject],
useFactory: (client: GraphQLClient) => getSdk(client),
},
],
})
export class AppModule {
// ...
}
Contributions welcome! Read the contribution guidelines first.
Link: https://github.com/golevelup/nestjs/tree/master/packages/graphql-request
#nest #nestjs #node #javascript #graphql
1661562514
npm install passport passport-jwt
npm install --save-dev @types/passport-jwt
@nestjs/passport
for authenticationnpm install @nestjs/passport
npm install @tfarras/nestjs-firebase-auth
To work with Firebase Auth you need to configure and initialize your firebase app. For this purpose you can use my module for firebase-admin.
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt } from 'passport-jwt';
import { FirebaseAuthStrategy } from '@tfarras/nestjs-firebase-auth';
@Injectable()
export class FirebaseStrategy extends PassportStrategy(FirebaseAuthStrategy, 'firebase') {
public constructor() {
super({
extractor: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
}
Note: You should provide an extractor. More information about passport-jwt extractors you can find here: http://www.passportjs.org/packages/passport-jwt/#included-extractors
AuthModule
and provide created strategyimport { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { FirebaseStrategy } from "./firebase.strategy";
@Module({
imports: [PassportModule],
providers: [FirebaseStrategy],
exports: [FirebaseStrategy],
controllers: [],
})
export class AuthModule { }
Import AuthModule
into AppModule
import { FirebaseAdminCoreModule } from '@tfarras/nestjs-firebase-admin';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from 'nestjs-config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import * as path from 'path';
@Module({
imports: [
ConfigModule.load(path.resolve(__dirname, 'config', '**', '!(*.d).{ts,js}')),
FirebaseAdminCoreModule.forRootAsync({
useFactory: (config: ConfigService) => config.get('firebase'),
inject: [ConfigService],
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { FirebaseAdminSDK, FIREBASE_ADMIN_INJECT } from '@tfarras/nestjs-firebase-admin';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Inject(FIREBASE_ADMIN_INJECT) private readonly fireSDK: FirebaseAdminSDK,
) { }
@Get()
@UseGuards(AuthGuard('firebase'))
getHello() {
return this.fireSDK.auth().listUsers();
}
}
In cases when you want to validate also if user exists in your database, or anything else after successfull Firebase validation you can define custom validate
method in your strategy.
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { FirebaseAuthStrategy, FirebaseUser } from '@tfarras/nestjs-firebase-auth';
import { ExtractJwt } from 'passport-jwt';
@Injectable()
export class FirebaseStrategy extends PassportStrategy(FirebaseAuthStrategy, 'firebase') {
public constructor() {
super({
extractor: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
async validate(payload: FirebaseUser): Promise<FirebaseUser> {
// Do here whatever you want and return your user
return payload;
}
}
Author: tfarras
Source code: https://github.com/tfarras/nestjs-firebase-auth
#nest #nestjs #node #javascript
1658452680
Add Prisma support to your NestJS application.
Install the library by running its schematics:
nest add nestjs-prisma
or us your package manager to install the library directly:
# npm
npm install nestjs-prisma
# yarn
yarn add nestjs-prisma
Example output of the schematics:
β Package installation in progress... β
Starting library setup...
? Which datasource provider do you want to use for `prisma init`? postgresql
? Do you like to Dockerize your application? (Supports postgresql and mysql) Yes
β
οΈ Added prisma@latest
β
οΈ Added @prisma/client@latest
β
οΈ Added Prisma scripts [6]
β
οΈ Added Prisma Seed script
β
οΈ Added Docker file
β
οΈ Added Docker Compose and .env
β
οΈ Add "prisma" directory to "excludes" in tsconfig.build.json
CREATE .dockerignore (42 bytes)
CREATE Dockerfile (455 bytes)
CREATE .env (642 bytes)
CREATE docker-compose.yml (497 bytes)
UPDATE package.json (2754 bytes)
UPDATE tsconfig.build.json (130 bytes)
β Packages installed successfully.
β Packages installed successfully.
β
οΈ Initialized Prisma - Datasource postgresql
PrismaService
and PrismaModule
provided by nestjs-prismaAdd PrismaModule
to the imports
section in your AppModule
or other modules to gain access to PrismaService
.
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [PrismaModule.forRoot()],
})
export class AppModule {}
Handle Prisma shutdown signal to shutdown your Nest application.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaService } from 'nestjs-prisma';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// enable shutdown hook
const prismaService: PrismaService = app.get(PrismaService);
prismaService.enableShutdownHooks(app);
await app.listen(3000);
}
bootstrap();
Apply Prisma Middlewares with PrismaModule
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRoot({
prismaServiceOptions: {
middlewares: [
async (params, next) => {
// Before query: change params
const result = await next(params);
// After query: result
return result;
},
], // see example loggingMiddleware below
},
}),
],
})
export class AppModule {}
Here is an example for using a Logging middleware.
Create your Prisma Middleware and export it as a function
// src/logging-middleware.ts
import { Prisma } from '@prisma/client';
export function loggingMiddleware(): Prisma.Middleware {
return async (params, next) => {
const before = Date.now();
const result = await next(params);
const after = Date.now();
console.log(
`Query ${params.model}.${params.action} took ${after - before}ms`,
);
return result;
};
}
Now import your Middleware and add the function into the middlewares
array.
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
import { loggingMiddleware } from './logging-middleware';
@Module({
imports: [
PrismaModule.forRoot({
prismaServiceOptions: {
middlewares: [loggingMiddleware()],
},
}),
],
})
export class AppModule {}
PrismaModule
configurationPrismaModule
allows to be used globally and to pass options to the PrismaClient
.
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRoot({
isGlobal: true,
prismaServiceOptions: {
prismaOptions: { log: ['info'] },
explicitConnect: true,
},
}),
],
})
export class AppModule {}
Additionally, PrismaModule
provides a forRootAsync
to pass options asynchronously. One option is to use a factory function:
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRootAsync({
isGlobal: true,
useFactory: () => ({
prismaOptions: {
log: ['info', 'query'],
},
explicitConnect: false,
}),
}),
],
})
export class AppModule {}
You can inject dependencies such as ConfigModule
to load options from .env files.
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
PrismaModule.forRootAsync({
isGlobal: true,
useFactory: async (configService: ConfigService) => {
return {
prismaOptions: {
log: [configService.get('log')],
datasources: {
db: {
url: configService.get('DATABASE_URL'),
},
},
},
explicitConnect: configService.get('explicit'),
};
},
inject: [ConfigService],
}),
],
})
export class AppModule {}
Alternatively, you can use a class instead of a factory:
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
PrismaModule.forRootAsync({
isGlobal: true,
useClass: PrismaConfigService,
}),
],
})
export class AppModule {}
Create the PrismaConfigService
and extend it with the PrismaOptionsFactory
import { Injectable } from '@nestjs/common';
import { PrismaOptionsFactory, PrismaServiceOptions } from 'nestjs-prisma';
@Injectable()
export class PrismaConfigService implements PrismaOptionsFactory {
constructor() {
// TODO inject any other service here like the `ConfigService`
}
createPrismaOptions(): PrismaServiceOptions | Promise<PrismaServiceOptions> {
return {
prismaOptions: {
log: ['info', 'query'],
},
explicitConnect: true,
};
}
}
PrismaService
and PrismaModule
nest add nestjs-prisma --addPrismaService
Add the flag --addPrismaService
if you like to generate your own PrismaService
and PrismaModule
for further customizations. Add PrismaModule
to the imports
section in your AppModule
or other modules to gain access to PrismaService
.
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
})
export class AppModule {}
Note: It is safe to remove
nestjs-prisma
as dependency otherwise you have two import suggestions forPrismaService
andPrismaModule
.
nestjs-prisma
provides a PrismaClientExceptionFilter
to catch unhandled PrismaClientKnownRequestError and returns different HttpStatus codes instead of 500 Internal server error
.
To use the filter you have the following two options.
main.ts
and pass the HttpAdapterHost
//src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaClientExceptionFilter } from 'nestjs-prisma';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter));
await app.listen(3000);
}
bootstrap();
Optionally, provide your own error code mapping via the constructor:
//src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaClientExceptionFilter } from 'nestjs-prisma';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(
new PrismaClientExceptionFilter(httpAdapter, {
// Prisma Error Code: Http Status Response
P2000: HttpStatus.BAD_REQUEST,
P2002: HttpStatus.CONFLICT,
P2025: HttpStatus.NOT_FOUND,
}),
);
await app.listen(3000);
}
bootstrap();
See the list of Prisma CLient Errors in the Prisma docs.
This will override the default error code mapping:
Error Code | Http Status |
---|---|
P2000 - "The provided value for the column is too long for the column's type. Column: {column_name}" | Bad Request - 400 |
P2002 - "Unique constraint failed on the {constraint}" | Conflict - 409 |
P2025 - "An operation failed because it depends on one or more records that were required but not found. {cause}" | Not Found - 404 |
APP_FILTER
token in any module//src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { PrismaClientExceptionFilter } from 'nestjs-prisma';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: PrismaClientExceptionFilter,
},
],
})
export class AppModule {}
All available flags:
Flag | Description | Type | Default |
---|---|---|---|
datasourceProvider | Specifies the datasource provider for prisma init and docker. | boolean | Prompted |
addDocker | Create a Dockerfile and docker-compose.yaml. | boolean | Prompted |
addPrismaService | Create a Prisma service extending the Prisma Client and module. | boolean | false |
dockerNodeImageVersion | Node version for the builder and runner image. | string | 14 |
name | The name for the Prisma service extending the Prisma Client and module. | string | Prisma |
prismaVersion | The Prisma version to be installed. | string | latest |
skipInstall | Skip installing dependency packages. | boolean | false |
skipPrismaInit | Skip initializing Prisma. | boolean | false |
You can pass additional flags to customize the schematic. For example, if you want to install a different version for Prisma use --prismaVersion
flag:
nest add nestjs-prisma --prismaVersion 3.2.1
If you want to skip installing dependencies use --skipInstall
flag:
nest add nestjs-prisma --skipInstall
Add Dockerfile
and docker-compose.yaml
, you can even use a different node
version (14-alpine
or 16
).
Currently uses PostgreSQL as a default database in
docker-compose.yaml
.
nest add nestjs-prisma --addDocker --dockerNodeImageVersion 14-alpine
Install @angular-devkit/schematics-cli
to be able to use schematics
command
npm i -g @angular-devkit/schematics-cli
Now build the schematics and run the schematic.
npm run build:schematics
# or
npm run dev:schematics
# dry-run
schematics .:nest-add
# execute schematics
schematics .:nest-add --debug false
# or
schematics .:nest-add --dry-run false
Helpful article about Custom Angular Schematics which also applies to Nest.
Download Details:
Author: notiz-dev
Source Code: https://github.com/notiz-dev/nestjs-prisma
License: MIT license
#nest #nestjs #node #javascript #prisma
1658444580
Each folder is a fully functional Nest application on its own. All you need to do after cloning the repository is move into the folder, install the dependencies, and test! Or you could just, you know, look at the test on GitHub too, no big deal.
I've noticed a lot of people seem to get stuck on testing, and how to do it using the utilities given to us by Nest. Hopefully with a community driven repository of tests it will help others get a better understanding and feel for how to get tests running.
This is not necessarily the Nest canonical way to test an application, nor is it the only way in any scenario. I will do my best to show different ways to run tests, but I will more than likely miss certain ways of running the tests, whether it is mocking in a specific way or otherwise. Again, this is a community maintained project and not a part of the official documentation. If you find other ways to test applications, great, I'd love to see them, but please do not think this is the only way.
git clone https://github.com/jmcdo29/testing-nestjs.git
cd testing-nestjs/<folderName>
npm install
OR yarn add
npm run test
OR yarn test
Did I miss your favorite topic? Did I miss out on something you absolutely need tested? Well then open an issue, or better yet, a pull request! I'll be happy to look over any topics and try to help figure out how to test them or to merge any PRs that add to the value of the repository. If you do end up making a pull request, please add a screenshot of the test coverage so others can see how well the tests run. The complex-sample branch has a good example.
The change log can be found on the Releases page.
Jay McDoniel and contributors. Thanks to everyone who's helping with this effort!
MIT License, see the included License.md file.
Download Details:
Author: jmcdo29
Source Code: https://github.com/jmcdo29/testing-nestjs
License: MIT license
#nest #nestjs #node #javascript #testing