feat(wip): initial boilerplate

This commit is contained in:
Hasta Ragil Saputra
2021-10-04 16:29:05 +07:00
commit 1b645380e8
23 changed files with 6575 additions and 0 deletions

48
src/app.module.ts Normal file
View File

@@ -0,0 +1,48 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';
import { UsersModule } from './users/users.module';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_CLIENT: Joi.valid('mysql', 'postgres'),
DATABASE_HOST: Joi.string(),
DATABASE_NAME: Joi.string(),
DATABASE_USERNAME: Joi.string(),
DATABASE_PASSWORD: Joi.string().empty('').default(''),
DATABASE_PORT: Joi.number().default(5432),
}),
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
return {
type: configService.get<'postgres' | 'mysql'>('database.client'),
host: configService.get<string>('database.host'),
port: configService.get<number>('database.port'),
username: configService.get<string>('database.username'),
password: configService.get<string>('database.password'),
database: configService.get<string>('database.name'),
entities: [],
synchronize: true,
autoLoadEntities: true,
logging: true,
namingStrategy: new SnakeNamingStrategy(),
};
},
inject: [ConfigService],
}),
UsersModule,
],
})
export class AppModule {}

View File

@@ -0,0 +1,13 @@
export default () => {
return {
port: parseInt(process.env.PORT, 10) || 3000,
database: {
client: process.env.DATABASE_CLIENT,
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD || '',
name: process.env.DATABASE_NAME,
},
};
};

24
src/main.ts Normal file
View File

@@ -0,0 +1,24 @@
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.listen(3000);
}
bootstrap();

View File

@@ -0,0 +1,9 @@
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
firstName: string;
@IsNotEmpty()
lastName: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}

View File

@@ -0,0 +1,45 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
CreateDateColumn,
} from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn({
type: 'timestamp with time zone',
nullable: false,
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp with time zone',
nullable: false,
})
updatedAt: Date;
@DeleteDateColumn({
type: 'timestamp with time zone',
nullable: true,
})
deletedAt: Date;
@VersionColumn()
version: number;
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,71 @@
import {
Controller,
Get,
Post,
Body,
Put,
Param,
Delete,
ParseUUIDPipe,
HttpStatus,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
return {
data: await this.usersService.create(createUserDto),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Get()
async findAll() {
const [data, count] = await this.usersService.findAll();
return {
data,
count,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get(':id')
async findOne(@Param('id', ParseUUIDPipe) id: string) {
return {
data: await this.usersService.findOne(id),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Put(':id')
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return {
data: await this.usersService.update(id, updateUserDto),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Delete(':id')
async remove(@Param('id', ParseUUIDPipe) id: string) {
await this.usersService.remove(id);
return {
statusCode: HttpStatus.OK,
message: 'success',
};
}
}

12
src/users/users.module.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,18 @@
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>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,84 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { EntityNotFoundError, Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto) {
const result = await this.usersRepository.insert(createUserDto);
return this.usersRepository.findOneOrFail(result.identifiers[0].id);
}
findAll() {
return this.usersRepository.findAndCount();
}
async findOne(id: string) {
try {
return await this.usersRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async update(id: string, updateUserDto: UpdateUserDto) {
try {
await this.usersRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
const result = await this.usersRepository.update(id, updateUserDto);
return this.usersRepository.findOneOrFail(id);
}
async remove(id: string) {
try {
await this.usersRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
await this.usersRepository.delete(id);
}
}