Repository: onwuvic/nest-blog-api
Branch: master
Commit: ffda9a4ac9ce
Files: 43
Total size: 25.3 KB
Directory structure:
gitextract_35nzldb_/
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package.json
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── core/
│ │ ├── constants/
│ │ │ └── index.ts
│ │ ├── database/
│ │ │ ├── database.config.ts
│ │ │ ├── database.module.ts
│ │ │ ├── database.providers.ts
│ │ │ └── interfaces/
│ │ │ └── dbConfig.interface.ts
│ │ ├── guards/
│ │ │ └── doesUserExist.guard.ts
│ │ └── pipes/
│ │ └── validate.pipe.ts
│ ├── main.ts
│ └── modules/
│ ├── auth/
│ │ ├── auth.controller.spec.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── local.strategy.ts
│ ├── posts/
│ │ ├── dto/
│ │ │ └── post.dto.ts
│ │ ├── post.entity.ts
│ │ ├── posts.controller.spec.ts
│ │ ├── posts.controller.ts
│ │ ├── posts.module.ts
│ │ ├── posts.providers.ts
│ │ ├── posts.service.spec.ts
│ │ └── posts.service.ts
│ └── users/
│ ├── dto/
│ │ └── user.dto.ts
│ ├── user.entity.ts
│ ├── users.module.ts
│ ├── users.providers.ts
│ ├── users.service.spec.ts
│ └── users.service.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.env
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true,
"trailingComma": "all"
}
================================================
FILE: README.md
================================================
A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
## Description
A Simple CRUD Web API with NestJs, Postgres, Sequelize ORM.
## Installation
```bash
$ git clone https://github.com/onwuvic/nest-blog-api.git
```
## Running the app
- cd into `nest-blog-api`
- run `npm install`
- set up your postgres database
- rename `.env.sample` to `.env` and populate the required parameters
- run `npm run start:dev`
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Stay in touch
- Author - Victor Onwuzor
- Twitter - [@victoronwuzor](https://twitter.com/victoronwuzor)
## License
[MIT licensed](LICENSE).
================================================
FILE: nest-cli.json
================================================
{
"language": "ts",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
================================================
FILE: package.json
================================================
{
"name": "nest-blog-api",
"version": "0.0.1",
"description": "",
"author": "",
"license": "MIT",
"scripts": {
"build": "rimraf dist && tsc -p tsconfig.build.json",
"format": "prettier --write \"src/**/*.ts\"",
"start": "ts-node -r tsconfig-paths/register src/main.ts",
"start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"",
"start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"",
"start:prod": "node dist/main.js",
"lint": "tslint -p tsconfig.json -c tslint.json",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^6.0.0",
"@nestjs/config": "0.4.0",
"@nestjs/core": "^6.0.0",
"@nestjs/jwt": "7.0.0",
"@nestjs/passport": "7.0.0",
"@nestjs/platform-express": "^6.0.0",
"bcrypt": "^5.0.0",
"class-transformer": "0.2.3",
"class-validator": "0.12.2",
"dotenv": "8.2.0",
"passport": "0.4.1",
"passport-jwt": "4.0.0",
"passport-local": "1.0.0",
"pg": "7.18.2",
"pg-hstore": "2.3.3",
"reflect-metadata": "^0.1.12",
"rimraf": "^2.6.2",
"rxjs": "^6.3.3",
"sequelize": "5.21.5",
"sequelize-typescript": "1.1.0"
},
"devDependencies": {
"@nestjs/testing": "^6.0.0",
"@types/express": "4.16.1",
"@types/jest": "24.0.11",
"@types/node": "11.13.4",
"@types/passport-jwt": "3.0.3",
"@types/passport-local": "1.0.33",
"@types/sequelize": "4.28.8",
"@types/supertest": "2.0.7",
"jest": "24.7.1",
"prettier": "1.17.0",
"supertest": "4.0.2",
"ts-jest": "^26.4.1",
"ts-node": "8.1.0",
"tsc-watch": "2.2.1",
"tsconfig-paths": "3.8.0",
"tslint": "5.16.0",
"typescript": "3.7.2"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
================================================
FILE: src/app.controller.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
================================================
FILE: src/app.controller.ts
================================================
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
================================================
FILE: src/app.module.ts
================================================
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DatabaseModule } from './core/database/database.module';
import { UsersModule } from './modules/users/users.module';
import { AuthModule } from './modules/auth/auth.module';
import { PostsModule } from './modules/posts/posts.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
DatabaseModule,
UsersModule,
AuthModule,
PostsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
================================================
FILE: src/app.service.ts
================================================
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
================================================
FILE: src/core/constants/index.ts
================================================
export const SEQUELIZE = 'SEQUELIZE';
export const DEVELOPMENT = 'development';
export const TEST = 'test';
export const PRODUCTION = 'production';
export const USER_REPOSITORY = 'USER_REPOSITORY';
export const POST_REPOSITORY = 'POST_REPOSITORY';
================================================
FILE: src/core/database/database.config.ts
================================================
import * as dotenv from 'dotenv';
import { IDatabaseConfig } from './interfaces/dbConfig.interface';
dotenv.config();
export const databaseConfig: IDatabaseConfig = {
development: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME_DEVELOPMENT,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
},
test: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME_TEST,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
},
production: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME_PRODUCTION,
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
},
};
================================================
FILE: src/core/database/database.module.ts
================================================
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
providers: [...databaseProviders],
exports: [...databaseProviders],
})
export class DatabaseModule { }
================================================
FILE: src/core/database/database.providers.ts
================================================
import { Sequelize } from 'sequelize-typescript';
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from '../constants';
import { databaseConfig } from './database.config';
import { User } from '../../modules/users/user.entity';
import { Post } from '../../modules/posts/post.entity';
export const databaseProviders = [
{
provide: SEQUELIZE,
useFactory: async () => {
let config;
switch (process.env.NODE_ENV) {
case DEVELOPMENT:
config = databaseConfig.development;
break;
case TEST:
config = databaseConfig.test;
break;
case PRODUCTION:
config = databaseConfig.production;
break;
default:
config = databaseConfig.development;
}
const sequelize = new Sequelize(config);
sequelize.addModels([User, Post]);
await sequelize.sync();
return sequelize;
},
},
];
================================================
FILE: src/core/database/interfaces/dbConfig.interface.ts
================================================
export interface IDatabaseConfigAttributes {
username?: string;
password?: string;
database?: string;
host?: string;
port?: number | string;
dialect?: string;
urlDatabase?: string;
}
export interface IDatabaseConfig {
development: IDatabaseConfigAttributes;
test: IDatabaseConfigAttributes;
production: IDatabaseConfigAttributes;
}
================================================
FILE: src/core/guards/doesUserExist.guard.ts
================================================
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { UsersService } from '../../modules/users/users.service';
@Injectable()
export class DoesUserExist implements CanActivate {
constructor(private readonly userService: UsersService) { }
canActivate(
context: ExecutionContext,
): boolean | Promise | Observable {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
async validateRequest(request) {
const userExist = await this.userService.findOneByEmail(request.body.email);
if (userExist) {
throw new ForbiddenException('This email already exist');
}
return true;
}
}
================================================
FILE: src/core/pipes/validate.pipe.ts
================================================
import { Injectable, ArgumentMetadata, BadRequestException, ValidationPipe, UnprocessableEntityException } from '@nestjs/common';
@Injectable()
export class ValidateInputPipe extends ValidationPipe {
public async transform(value, metadata: ArgumentMetadata) {
try {
return await super.transform(value, metadata);
} catch (e) {
if (e instanceof BadRequestException) {
throw new UnprocessableEntityException(this.handleError(e.message.message));
}
}
}
private handleError(errors) {
return errors.map(error => error.constraints);
}
}
================================================
FILE: src/main.ts
================================================
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidateInputPipe } from './core/pipes/validate.pipe';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
// handle all user input validation globally
app.useGlobalPipes(new ValidateInputPipe());
await app.listen(3000);
}
bootstrap();
================================================
FILE: src/modules/auth/auth.controller.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('Auth Controller', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
================================================
FILE: src/modules/auth/auth.controller.ts
================================================
import { Controller, Body, Post, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { UserDto } from '../users/dto/user.dto';
import { DoesUserExist } from '../../core/guards/doesUserExist.guard';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) { }
@UseGuards(AuthGuard('local'))
@Post('login')
async login(@Request() req) {
return await this.authService.login(req.user);
}
@UseGuards(DoesUserExist)
@Post('signup')
async signUp(@Body() user: UserDto) {
return await this.authService.create(user);
}
}
================================================
FILE: src/modules/auth/auth.module.ts
================================================
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
PassportModule,
UsersModule,
JwtModule.register({
secret: process.env.JWTKEY,
signOptions: { expiresIn: process.env.TOKEN_EXPIRATION },
}),
],
providers: [
AuthService,
LocalStrategy,
JwtStrategy,
],
controllers: [AuthController],
})
export class AuthModule {}
================================================
FILE: src/modules/auth/auth.service.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
================================================
FILE: src/modules/auth/auth.service.ts
================================================
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UsersService,
private readonly jwtService: JwtService,
) { }
async validateUser(username: string, pass: string) {
// find if user exist with this email
const user = await this.userService.findOneByEmail(username);
if (!user) {
return null;
}
// find if user password match
const match = await this.comparePassword(pass, user.password);
if (!match) {
return null;
}
// tslint:disable-next-line: no-string-literal
const { password, ...result } = user['dataValues'];
return result;
}
public async login(user) {
const token = await this.generateToken(user);
return { user, token };
}
public async create(user) {
// hash the password
const pass = await this.hashPassword(user.password);
// create the user
const newUser = await this.userService.create({ ...user, password: pass });
// tslint:disable-next-line: no-string-literal
const { password, ...result } = newUser['dataValues'];
// generate token
const token = await this.generateToken(result);
// return the user and the token
return { user: result, token };
}
private async generateToken(user) {
const token = await this.jwtService.signAsync(user);
return token;
}
private async hashPassword(password) {
const hash = await bcrypt.hash(password, 10);
return hash;
}
private async comparePassword(enteredPassword, dbPassword) {
const match = await bcrypt.compare(enteredPassword, dbPassword);
return match;
}
}
================================================
FILE: src/modules/auth/jwt.strategy.ts
================================================
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UsersService } from '../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly userService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWTKEY,
});
}
async validate(payload: any) {
// check if user in the token actually exist
const user = await this.userService.findOneById(payload.id);
if (!user) {
throw new UnauthorizedException('You are not authorized to perform the operation');
}
return payload;
}
}
================================================
FILE: src/modules/auth/local.strategy.ts
================================================
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException('Invalid user credentials');
}
return user;
}
}
================================================
FILE: src/modules/posts/dto/post.dto.ts
================================================
import { IsNotEmpty, MinLength } from 'class-validator';
export class PostDto {
@IsNotEmpty()
@MinLength(4)
readonly title: string;
@IsNotEmpty()
readonly body: string;
}
================================================
FILE: src/modules/posts/post.entity.ts
================================================
import { Table, Column, Model, DataType, ForeignKey, BelongsTo } from 'sequelize-typescript';
import { User } from '../users/user.entity';
@Table
export class Post extends Model {
@Column({
type: DataType.STRING,
allowNull: false,
})
title: string;
@Column({
type: DataType.TEXT,
allowNull: false,
})
body: string;
@ForeignKey(() => User)
@Column({
type: DataType.INTEGER,
allowNull: false,
})
userId: number;
@BelongsTo(() => User)
user: User;
}
================================================
FILE: src/modules/posts/posts.controller.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { PostsController } from './posts.controller';
describe('Posts Controller', () => {
let controller: PostsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PostsController],
}).compile();
controller = module.get(PostsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
================================================
FILE: src/modules/posts/posts.controller.ts
================================================
import { Controller, Get, Post, Put, Delete, Param, Body, NotFoundException, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { PostsService } from './posts.service';
import { Post as PostEntity } from './post.entity';
import { PostDto } from './dto/post.dto';
@Controller('posts')
export class PostsController {
constructor(private readonly postService: PostsService) { }
@Get()
async findAll() {
// get all posts in the db
return await this.postService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: number): Promise {
// find the post with this id
const post = await this.postService.findOne(id);
// if the post doesn't exit in the db, throw a 404 error
if (!post) {
throw new NotFoundException('This Post doesn\'t exist');
}
// if post exist, return the post
return post;
}
@UseGuards(AuthGuard('jwt'))
@Post()
async create(@Body() post: PostDto, @Request() req): Promise {
// create a new post and return the newly created post
return await this.postService.create(post, req.user.id);
}
@UseGuards(AuthGuard('jwt'))
@Put(':id')
async update(@Param('id') id: number, @Body() post: PostDto, @Request() req): Promise {
// get the number of row affected and the updated post
const { numberOfAffectedRows, updatedPost } = await this.postService.update(id, post, req.user.id);
// if the number of row affected is zero, it means the post doesn't exist in our db
if (numberOfAffectedRows === 0) {
throw new NotFoundException('This Post doesn\'t exist');
}
// return the updated post
return updatedPost;
}
@UseGuards(AuthGuard('jwt'))
@Delete(':id')
async remove(@Param('id') id: number, @Request() req) {
// delete the post with this id
const deleted = await this.postService.delete(id, req.user.id);
// if the number of row affected is zero, then the post doesn't exist in our db
if (deleted === 0) {
throw new NotFoundException('This Post doesn\'t exist');
}
// return success message
return 'Successfully deleted';
}
}
================================================
FILE: src/modules/posts/posts.module.ts
================================================
import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { postsProviders } from './posts.providers';
@Module({
providers: [PostsService, ...postsProviders],
controllers: [PostsController],
})
export class PostsModule {}
================================================
FILE: src/modules/posts/posts.providers.ts
================================================
import { Post } from './post.entity';
import { POST_REPOSITORY } from '../../core/constants';
export const postsProviders = [
{
provide: POST_REPOSITORY,
useValue: Post,
},
];
================================================
FILE: src/modules/posts/posts.service.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { PostsService } from './posts.service';
describe('PostsService', () => {
let service: PostsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PostsService],
}).compile();
service = module.get(PostsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
================================================
FILE: src/modules/posts/posts.service.ts
================================================
import { Injectable, Inject } from '@nestjs/common';
import { Post } from './post.entity';
import { PostDto } from './dto/post.dto';
import { User } from '../users/user.entity';
import { POST_REPOSITORY } from '../../core/constants';
@Injectable()
export class PostsService {
constructor(@Inject(POST_REPOSITORY) private readonly postRepository: typeof Post) { }
async create(post: PostDto, userId): Promise {
return await this.postRepository.create({ ...post, userId });
}
async findAll(): Promise {
return await this.postRepository.findAll({
include: [{ model: User, attributes: { exclude: ['password'] } }],
});
}
async findOne(id): Promise {
return await this.postRepository.findOne({
where: { id },
include: [{ model: User, attributes: { exclude: ['password'] } }],
});
}
async delete(id, userId) {
return await this.postRepository.destroy({ where: { id, userId } });
}
async update(id, data, userId) {
const [numberOfAffectedRows, [updatedPost]] = await this.postRepository.update({ ...data }, { where: { id, userId }, returning: true });
return { numberOfAffectedRows, updatedPost };
}
}
================================================
FILE: src/modules/users/dto/user.dto.ts
================================================
import { IsNotEmpty, MinLength, IsEmail, IsEnum } from 'class-validator';
enum Gender {
MALE = 'male',
FEMALE = 'female',
}
export class UserDto {
@IsNotEmpty()
readonly name: string;
@IsNotEmpty()
@IsEmail()
readonly email: string;
@IsNotEmpty()
@MinLength(6)
readonly password: string;
@IsNotEmpty()
@IsEnum(Gender, {
message: 'gender must be either male or female',
})
readonly gender: Gender;
}
================================================
FILE: src/modules/users/user.entity.ts
================================================
import { Table, Column, Model, DataType } from 'sequelize-typescript';
@Table
export class User extends Model {
@Column({
type: DataType.STRING,
allowNull: false,
})
name: string;
@Column({
type: DataType.STRING,
unique: true,
allowNull: false,
})
email: string;
@Column({
type: DataType.STRING,
allowNull: false,
})
password: string;
@Column({
type: DataType.ENUM,
values: ['male', 'female'],
allowNull: false,
})
gender: string;
}
================================================
FILE: src/modules/users/users.module.ts
================================================
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { usersProviders } from './users.providers';
@Module({
providers: [UsersService, ...usersProviders],
exports: [UsersService],
})
export class UsersModule {}
================================================
FILE: src/modules/users/users.providers.ts
================================================
import { User } from './user.entity';
import { USER_REPOSITORY } from '../../core/constants';
export const usersProviders = [
{
provide: USER_REPOSITORY,
useValue: User,
},
];
================================================
FILE: src/modules/users/users.service.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
================================================
FILE: src/modules/users/users.service.ts
================================================
import { Injectable, Inject } from '@nestjs/common';
import { User } from './user.entity';
import { UserDto } from './dto/user.dto';
import { USER_REPOSITORY } from '../../core/constants';
@Injectable()
export class UsersService {
constructor(@Inject(USER_REPOSITORY) private readonly userRepository: typeof User) { }
async create(user: UserDto): Promise {
return await this.userRepository.create(user);
}
async findOneByEmail(email: string): Promise {
return await this.userRepository.findOne({ where: { email } });
}
async findOneById(id: number): Promise {
return await this.userRepository.findOne({ where: { id } });
}
}
================================================
FILE: test/app.e2e-spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
================================================
FILE: test/jest-e2e.json
================================================
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
================================================
FILE: tsconfig.build.json
================================================
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true
},
"exclude": ["node_modules", "dist"]
}
================================================
FILE: tslint.json
================================================
{
"defaultSeverity": "error",
"extends": ["tslint:recommended"],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"quotemark": [true, "single"],
"member-access": [false],
"ordered-imports": [false],
"max-line-length": [true, 150],
"member-ordering": [false],
"interface-name": [false],
"arrow-parens": false,
"object-literal-sort-keys": false
},
"rulesDirectory": []
}