npm i -g @nestjs/cli
nestjs new nestjs-task-management
nest g module tasks
- Creates a file src/tasks/tasks.module.ts
- Imports it into the app module in src
- Uses
@Module()decorator on the exported class
nest g controller tasks --no-spec
- Creates a file src/tasks/tasks.controller.ts
- Bound to a specific path
- Contains handlers that process endpoints and request methods
- Uses dependency injection to access other providers
- Uses the
@Controller()decorator to define the path for that controller
- Handlers are methods in the controller class
- They are decorated with
@Get(), @Post(), @Delete(), etc decorators - The handler processes the request that correlates to the decorator
- Can be a plain value, a class, a sync/async factory, etc
- Can be injected into constructors if decorated with
@Injectable - Are provided to modules to make them work
- In the
providerskey of the config
- In the
- Can be exported from a module
- A type of Provider
- Main source of business logic
- Called from Controller for things like validation, db access, etc
- When wrapped with
@Injectable()and provided to a module, they are created as singletons - If you add a service as an argument to the constructor of a Controller class, it is injected as a class property
- When nestjs instantiates the Tasks Controller, it will automatically pass the service in because the service is registered as a provider on the module
nest g service tasks --no-spec
- Helps with separation of concerns
- Separates the business logic from the HTTP handlers
- Injects a type, so you can swap services with keeping the same interface
- src/tasks/task.model.ts
- Class or interface defining the data objects
- It defines the properties on the objects you're passing around
- In the service
- CRUD operations here
- In the controller
- Call the service methods
- Decorate the controller method params with
@Body('inputFieldNamee') inputParamName: stringarguments to automatically access the request body
- Data Transfer
- Use classes not interfaces
- Encapsulate the shape of the object you're passing around to create/modify models
- Defines how data is sent over the network
- Useful for validation
- Not the same as models
- Should be for a purpose, like the fields you need for a create operation
- Pipes operate the arguments passed to the route handler, before the handler is invoked
- Pipes can perform data transformation or data validation
- Pipes can return data - either the original or modified, which is then passed to the route handler
- Pipes can throw exceptions, which are handled by Nestjs and parsed into error responses
- Pipes can be async
- Comes with some default installed pipes
- ValidationPipe - Validates an object against a class (e.g. DTOs). If it cannot be mapped, it fails
- ParseIntPipe - Handles converting HTTP body strings into Numbers
- Custom pipes
- Pipes are classes that implement the PipeTransform interface, which requires a
transform()method - Pipes are annotated with the
@Injectabledecorator transform()accepts two argumentsvalue- the value of the argumentmetadata- optional, an object containing metadata about the argument- The return value of the
transformmethod is passed to the handler - The exceptions are returned to the client as http errors
- Pipes can be Parameter (i.e. Controller)- level pipes or Handler level
- Parameter-level pipes are slimmer and cleaner
- Often result in additional code added to handlers :-(
- Handler-level pipes require more code, but are cleaner
- Don't need extra code at the parameter level
- Easier to maintain and extend
- Responsibility of identifying arguments to process is moved into the central location of the pipe
- Promotes usage of DTOs
- Parameter-level pipes are slimmer and cleaner
- Pipes are classes that implement the PipeTransform interface, which requires a
npm i -S class-validator class-transformer- Class validator provides a bunch of helpful decorators to enforce type
- Create new pipe file
mkdir src/tasks/pipes && touch src/tasks/pipes/task-status-validator.pipe.ts
- https://typeorm.io
npm i -S typeorm @nestjs/typeorm pg- Create src/config/typeorm.config.ts for the db config
- Use the
TypeOrmModuleas an import in the app module, passing it the config in theforRootmethod - Register it in imports in the tasks module scope by calling
forFeatureand passing it the array of entities - Inject it into a service with a constructor param argument
- e.g.
constructor(@InjectRepository(TaskRepository) private taskRepository: TaskRepository){} - then
async someMethod() {const foo = await this.taskRepository.find({foo: 'bar'}); return foo;}
- e.g.
nest g module authnest g controller auth --no-specnest g service auth --no-spec- The auth module will use passport and a JWT strategy
npm i -S @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs- create
src/auth/jwt.strategy.tsto implement the validate methodvalidate(payload: JwtPayload)is called by passport after it automatically verifies that the token is valid- The method needs to hydrate the full user object from the token payload
- The full user object is stored in the request
- Create a
GetUserdecorator- The decorator is used in any route handler (in the handler method arguments) where you want to pull the user out of the request object and inject it into a handler (to pass it to a service method)
@nestjs/passportexposes a Guard,AuthGuardthat you can pass to the@UseGuardsdecorator either on a controller class or method.- This will authenticate that route
- The Auth service needs to implement a
signInandsignUpmethod- The signUp should delegate the logic for creating the user and hashing the password with a per-user hash to the repository for creating a new user
- The signOut method should validate the credentials using the bcrypt logic in the repository and then, in the service, create an access token using the jwtService (from
@nest/jwt) injected into the auth service constructor
- Import the Auth module into the App module
- Add the
PassportModule.register()andJwtModule.register()to theimportsof the auth module - Add the
AuthServiceandJwtStrategyto theprovidersarray - Export the
JwtStrategyandPassportModuleso they can be used by the other modules
- Add the
AuthModuleto the Task module imports - Now you can use
@UseGuards(AuthGuard())and@GetUser()to protect the routes against unauthenticated users and obtain the user data
-
General purpose logs
-
Warning - Unhandled issues, not fatal or destructive
-
Error - Fatal or destructive
-
Debug - Intended for devs
-
Verbose - Intended for operators
-
Nestjs ships with a logger in the
@nestjs/commonpackage -
Just add the logger as a property to your classes and call it.
- Defining values that are loaded at startup, not changed during runtime
- Configured per environment
- Defined in code or JSON,YML,etc
- Can also define in environment variables
npm i -S config
- Signup - https://auth0.com/signup
- Create Auth Tenant - https://auth0.com/docs/getting-started/create-tenant
- Isolated environment for users and service configuration
- Create Auth0 API - https://manage.auth0.com/ -> Applications -> APIs
- Used by application to process authentication and authorization requests
- Register Auth0 Client Appliation
- https://manage.auth0.com/ -> Applications -> Create Application -> SPA
- Connect client to API
- Update "Auth0 Callbck URL" and "Allowed Logout URLs" to the Client application url they should hit after auth
- Update the "Allowed Web Origins" field as well
- Disable CORS
- passport - authentication middleware
- @nestjs/passport - passport utility module for Nest
- passport-jwt - passport strategy authenticating with a JWT
- jwks-rsa - retrieves RSA signing keys from JWKS (JSON Web Key Set)
- Create permissions for the Menu API we created
- Create a role called
menu-admin - Assign permissions from Menu API to the
menu-adminrole
- Applications -> APIs -> Menu API -> Permissions
- Add the permissions
create:itemsread:itemsupdate:itemsdelete:items
- Applications -> APIs -> Menu API -> Settings
Enable RBAC- YesAdd Permissions in the Access Token- Yes
- https://manage.auth0.com/#/roles
- Create Role - "menu-admin"
- Description - "Create, update, and delete items"
- Permissions -> Add Permissions -> Menu API -> Select All
- On login, Auth0 sends 2 tokens to the client
-
Access Token
The access token is a credential that can be used by an application to access an API. The client passes this token when it calls an API. The token informs the API that the bearer of the token has the permissions encoded in the token.
-
ID Token
JWT containing the User attributes. Typically used by the client to obtain user profile details
-
- https://manage.auth0.com/#/rules
- Create Rule - Empty Rule -> "Add user roles to tokens"
function(user, context, callback) {
const namespace = 'https://menu-api.demo.com';
// If the user has roles, add those roles to the tokens
if (context.authorization && context.authorization.roles) {
const assignedRoles = context.authorization.roles;
if (context.idToken) {
const idTokenClaims = context.idToken;
idTokenClaims[`${namespace}/roles`] = assignedRoles;
context.idToken = idTokenClaims;
}
if (context.accessToken) {
const accessTokenClaims = context.accessToken;
accessTokenClaims[`${namespace}/roles`] = assignedRoles;
context.accessToken = accessTokenClaims;
}
}
callback(null, user, context);
}
After a user authenticates, this is executed, invoked with 3 arguments
- user - Returned by identity provider, e.g. Google or Auth0
- context - Context like user IP addr, application, etc
- callback - Allows you to pass in the modified tokens or an error
- Must invoke callback or the script will timeout
- User Management -> Users -> Create User
- Email:
some@email.com - Password:
yeahyouknow - Connection:
Username-Password-Authentication - ->
Create-> Auto nav to User page
- Email:
- (From user page) -> Roles Tab
- ->
Assign Roles - Select the
menu-adminrole from the dropdown ->Assign
- ->
- Attach custom metadata to a route handler using the
@Permissions()decorator - The custom metadata will specify the permissions needed to access that route
- e.g
create:items
- e.g
- Supply the permission data to a custom guard,
PermissionsGuard PermissionsGuardinspects the access token provided by the client and verifies that it has the required permissions.
nest generate decorator permissions --no-spec- This will use the
SetMetadatamethod to associate an array of strings representing permissions with apermissionskey. - The key-value pair will be attached to whatever method we decorate with
@Permissions
npx nest generate guard permissions --no-spec
- JwtStrategy / Passport validates the client request
- The Controller configured for the requested route begins handling the request
- The Controller matches the request verb/route combo with a route handler
- The Controller invokes the route Guards attached to the handler and invokes the handler if the Guard(s) return true or Promise
- These steps need to share data. Nest shares data using the
ExecutionContext - The
PermissionsGuarduses the NestJS built-in (injected) classReflectorto get thepermissionsmetadata placed on the handler by the@Permissions()deocorator- These are the required permissions to access the route
- The
PermissionsGuardthen uses the ExecutionContext to get the user object and retrieve the user's permissions - Finally, it compares the required permissions for the route to the permissions available on the user.