A place to store all classes and their dependencies AND instances that I have created
Internally, when we feed our controller to Nest, it will look over the constructor of the controller and register all dependencies into the DI container. Then it will create such dependency instance so a controller can be created.
The benefit of doing this is that we only create one copy of dependency and share it through controllers (i.e. services, repository)!
Injectable marks a class to register in container
add these classes into module decorator providers
1 2 3 4 5 6 7 8 9 10 11 12 13 14
export class MessagesService { messagesRepo: MessagesRepository; constructor(messagesRepo: MessagesRepository){ this.messagesRepo = messagesRepo; } } //Same as export class MessagesService { constructor(public messagesRepo: MessagesRepository){} }
export class MessagesService { constructor(private messagesRepo: MessagesRepository){} }
forRoot means the connection is shared through all modules!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Module({ imports: [ TypeOrmModule.forRoot({ type: 'sqlite', database: 'db.sqlite', entities: [], //at last, add User into this list synchronize: true, // only in dev: automatically update SQL table }), UsersModule, ReportsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // means: only allow those I defined in DTO to be validate }), ); await app.listen(3000); } bootstrap();
asyncupdate(id: number, attrs: Partial<User>) { const user = awaitthis.findOne(id); if (!user) { thrownewError('User not found'); } Object.assign(user, attrs); returnthis.repo.save(user); }
if we want to use entity , we need to first fetch the entity from DB and then update the entity. Object.assign allows attrs to be expanded and overwrites/add all attributes of user object. Then we call save(user)
We cannot handle NotFoundException at the service level because other transmitting protocol such as WebSocket does not support suck error handling. But in our case, we can do so as we do not aim to utilize this service in other protocols at this time.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { //Here runs before the handler // console.log('Before...');
//Here runs after the handler but before the response is sent return next.handle().pipe( map((data: any) => { return plainToClass(this.dto, data, { excludeExtraneousValues: true, }); }), ); } }
// users.controller.ts
//@UseInterceptors(new SerializeInterceptor(UserDto)); @Serialize(UserDto) @Get('/:id') asyncfindUser(@Param('id') id: string) { const user = awaitthis.usersService.findOne(parseInt(id)); if (!user) { thrownew NotFoundException('User not found'); } return user; }
asyncfunctionbootstrap() { const app = await NestFactory.create(AppModule); app.use( cookieSession({ keys: ['asdasd'], //for encryption purpose }), ); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // means: only allow those I defined in DTO to be validate }), ); await app.listen(3000); } bootstrap();
if (userId) { const user = awaitthis.usersService.findOne(userId); request.currentUser = user; }
return handler.handle(); } }
Since the decorator is not part of the dependency injection system, we need to use an interceptor to access UsersService to retrieve the current user based on the stored session userId
If we do not use the decorator we then need to pass @Request() request: Request in the controller method.
Don’t forget to add CurrentUserInterceptor into provider of users. module.ts
We need to apply CurrentUserInterceptor to the controller to make CurrentUser decorator into effect
AuthService will be registered as normally, when it is initialized in the DI container, the container will look over all its dependencies and try to create the instance.
The point comes: the second object means if any injectables or controller need to initialize a service UsersService, then DI container will use fakeUsersService!
1 2 3 4 5 6
//Create a fake copy of the usersService const fakeUsersService = { find: () => Promise.resolve([]), create: (email: string, password: string) => Promise.resolve({ id: 1, email, password }), };
This fakeUsersService object implements several methods that are needed for initializing AuthService!
find() and create() are all async, we need to return Promise
Promise. resolve()immediately return a resolved promise with the given value.
To help TypeScript to infer the needed methods for fakeUsersService
We can use Partial<UsersService> on fakeUsersService
since create() expects to return a user entity, the fake user entity is an object without method such as logRemove etc… Therefore, we can enforce it to User entity type.
import { Test } from '@nestjs/testing'; import { AuthService } from './auth.service'; import { User } from './user.entity'; import { UsersService } from './users.service';
describe('AuthService', () => { let service: AuthService;
// For every single test, gets a new AuthService beforeEach(async () => { //Create a fake copy of the usersService const fakeUsersService: Partial<UsersService> = { find: () => Promise.resolve([]), create: (email: string, password: string) => Promise.resolve({ id: 1, email, password } as User), };
// this is a DI container const module = await Test.createTestingModule({ providers: [ AuthService, { provide: UsersService, useValue: fakeUsersService, }, ], }).compile(); // Create a test Service service = module.get(AuthService); });
it('can create an instance of auth service', async () => { expect(service).toBeDefined(); }); });
it('throws an error if user signs up with email that is in use.', async () => { fakeUsersService.find = () => Promise.resolve([ { id: 1, email: 'abc@abc.com', password: 'qwe' } as User, ]); expect.assertions(2); // We expect it to fail in try, and catch allows test to be done // Jest will assume test is fail if it does not finish in 5 seconds try { await service.signup('qwe@qwe.com', 'qwe'); } catch (error) { expect(error).toBeInstanceOf(BadRequestException); expect(error.message).toEqual('User already exists'); } });
it('throws an error if user signs in with unused email', async () => { expect.assertions(2); try { await service.signin('asdasdasdqw@asdasda.com', 'qwe'); } catch (error) { expect(error).toBeInstanceOf(BadRequestException); expect(error.message).toEqual("User doesn't exist"); } });
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; const cookieSession = require('cookie-session');
async function bootstrap() { const app = await NestFactory.create(AppModule); app.use( cookieSession({ keys: ['super'], //for encryption purpose }), ); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // means: only allow those I defined in DTO to be validate }), ); await app.listen(3000); } bootstrap();
Downside of doing all this is that we migrate our pipe, middleware setting into app.module which is not very clear what we are doing with pipe and middleware
I would prefer the first simple method…then. For detail ,seeing the video
Besides, since in our query or param @Param("xxx") and @Query() in either Patch, put, get, it will parse all either string or number into string. therefore, we need to transform them in dto