浏览代码

feat: 初始化NestJS项目基础结构

添加项目基础文件和配置,包括:
- 核心模块(app.module, app.controller, app.service)
- 认证模块(auth.controller, auth.service)
- 文章和标签模块(post.controller, tag.controller)
- Prisma ORM配置和数据库模型
- 测试配置和基础测试用例
- 项目配置文件(tsconfig, prettier, eslint等)
- 开发环境配置(vscode调试配置)
Sakulin 2 月之前
当前提交
a77209e06b

+ 56 - 0
.gitignore

@@ -0,0 +1,56 @@
+# compiled output
+/dist
+/node_modules
+/build
+
+# Logs
+logs
+*.log
+npm-debug.log*
+pnpm-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
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# temp directory
+.temp
+.tmp
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

+ 4 - 0
.prettierrc

@@ -0,0 +1,4 @@
+{
+  "singleQuote": true,
+  "trailingComma": "all"
+}

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // 使用 IntelliSense 了解相关属性。 
+    // 悬停以查看现有属性的描述。
+    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        
+        {
+            "type": "node-terminal",
+            "name": "Run Script: prisma:generate",
+            "request": "launch",
+            "command": "npm run prisma:generate",
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}

+ 98 - 0
README.md

@@ -0,0 +1,98 @@
+<p align="center">
+  <a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
+</p>
+
+[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
+[circleci-url]: https://circleci.com/gh/nestjs/nest
+
+  <p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
+    <p align="center">
+<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
+<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
+<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
+<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
+<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
+<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
+<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
+  <a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
+    <a href="https://opencollective.com/nest#sponsor"  target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
+  <a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
+</p>
+  <!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
+  [![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
+
+## Description
+
+[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+
+## Project setup
+
+```bash
+$ npm install
+```
+
+## Compile and run the project
+
+```bash
+# development
+$ npm run start
+
+# watch mode
+$ npm run start:dev
+
+# production mode
+$ npm run start:prod
+```
+
+## Run tests
+
+```bash
+# unit tests
+$ npm run test
+
+# e2e tests
+$ npm run test:e2e
+
+# test coverage
+$ npm run test:cov
+```
+
+## Deployment
+
+When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
+
+If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
+
+```bash
+$ npm install -g @nestjs/mau
+$ mau deploy
+```
+
+With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+
+## Resources
+
+Check out a few resources that may come in handy when working with NestJS:
+
+- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
+- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
+- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
+- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
+- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
+- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
+- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
+- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+
+## Support
+
+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](https://docs.nestjs.com/support).
+
+## Stay in touch
+
+- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
+- Website - [https://nestjs.com](https://nestjs.com/)
+- Twitter - [@nestframework](https://twitter.com/nestframework)
+
+## License
+
+Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

+ 34 - 0
eslint.config.mjs

@@ -0,0 +1,34 @@
+// @ts-check
+import eslint from '@eslint/js';
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+  {
+    ignores: ['eslint.config.mjs'],
+  },
+  eslint.configs.recommended,
+  ...tseslint.configs.recommendedTypeChecked,
+  eslintPluginPrettierRecommended,
+  {
+    languageOptions: {
+      globals: {
+        ...globals.node,
+        ...globals.jest,
+      },
+      sourceType: 'commonjs',
+      parserOptions: {
+        projectService: true,
+        tsconfigRootDir: import.meta.dirname,
+      },
+    },
+  },
+  {
+    rules: {
+      '@typescript-eslint/no-explicit-any': 'off',
+      '@typescript-eslint/no-floating-promises': 'warn',
+      '@typescript-eslint/no-unsafe-argument': 'warn'
+    },
+  },
+);

+ 8 - 0
nest-cli.json

@@ -0,0 +1,8 @@
+{
+  "$schema": "https://json.schemastore.org/nest-cli",
+  "collection": "@nestjs/schematics",
+  "sourceRoot": "src",
+  "compilerOptions": {
+    "deleteOutDir": true
+  }
+}

+ 78 - 0
package.json

@@ -0,0 +1,78 @@
+{
+  "name": "bk-serve",
+  "version": "0.0.1",
+  "description": "",
+  "author": "",
+  "private": true,
+  "license": "UNLICENSED",
+  "scripts": {
+    "build": "nest build",
+    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+    "start": "nest start",
+    "start:dev": "nest start --watch",
+    "start:debug": "nest start --debug --watch",
+    "start:prod": "node dist/main",
+    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+    "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",
+    "prisma:generate": "npx prisma generate --schema=./prisma/schema.prisma",
+    "prisma:migrate": "npx prisma migrate dev --schema=./prisma/schema.prisma",
+    "prisma:studio": "npx prisma studio --schema=./prisma/schema.prisma"
+  },
+  "dependencies": {
+    "@nestjs/common": "^11.0.1",
+    "@nestjs/core": "^11.0.1",
+    "@nestjs/platform-express": "^11.0.1",
+    "@prisma/client": "^6.8.2",
+    "reflect-metadata": "^0.2.2",
+    "rxjs": "^7.8.1"
+  },
+  "devDependencies": {
+    "@eslint/eslintrc": "^3.2.0",
+    "@eslint/js": "^9.18.0",
+    "@nestjs/cli": "^11.0.0",
+    "@nestjs/schematics": "^11.0.0",
+    "@nestjs/testing": "^11.0.1",
+    "@swc/cli": "^0.6.0",
+    "@swc/core": "^1.10.7",
+    "@types/express": "^5.0.0",
+    "@types/jest": "^29.5.14",
+    "@types/node": "^22.10.7",
+    "@types/supertest": "^6.0.2",
+    "eslint": "^9.18.0",
+    "eslint-config-prettier": "^10.0.1",
+    "eslint-plugin-prettier": "^5.2.2",
+    "globals": "^16.0.0",
+    "jest": "^29.7.0",
+    "prettier": "^3.4.2",
+    "prisma": "^6.8.2",
+    "source-map-support": "^0.5.21",
+    "supertest": "^7.0.0",
+    "ts-jest": "^29.2.5",
+    "ts-loader": "^9.5.2",
+    "ts-node": "^10.9.2",
+    "tsconfig-paths": "^4.2.0",
+    "typescript": "^5.7.3",
+    "typescript-eslint": "^8.20.0"
+  },
+  "jest": {
+    "moduleFileExtensions": [
+      "js",
+      "json",
+      "ts"
+    ],
+    "rootDir": "src",
+    "testRegex": ".*\\.spec\\.ts$",
+    "transform": {
+      "^.+\\.(t|j)s$": "ts-jest"
+    },
+    "collectCoverageFrom": [
+      "**/*.(t|j)s"
+    ],
+    "coverageDirectory": "../coverage",
+    "testEnvironment": "node"
+  }
+}

+ 25 - 0
prisma/schema.prisma

@@ -0,0 +1,25 @@
+datasource db {
+    provider = "sqlite"
+    url      = "file:./db.sqlite"
+}
+
+generator client {
+  provider = "prisma-client-js"
+}
+
+model Tag {
+    id        Int     @id @default(autoincrement())
+    name      String  @unique
+    color     String // 创建随机鲜艳颜色
+    createdAt DateTime @default(now())
+    posts     Post[]
+}
+
+model Post {
+    id        Int     @id @default(autoincrement())
+    title     String
+    content   String
+    createdAt DateTime @default(now())
+    updatedAt DateTime @default(now())
+    tags      Tag[]
+}

+ 22 - 0
src/app.controller.spec.ts

@@ -0,0 +1,22 @@
+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>(AppController);
+  });
+
+  describe('root', () => {
+    it('should return "Hello World!"', () => {
+      expect(appController.getHello()).toBe('Hello World!');
+    });
+  });
+});

+ 12 - 0
src/app.controller.ts

@@ -0,0 +1,12 @@
+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();
+  }
+}

+ 14 - 0
src/app.module.ts

@@ -0,0 +1,14 @@
+import { Module } from '@nestjs/common';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+import { PostController } from './post/post.controller';
+import { AuthController } from './auth/auth.controller';
+import { AuthService } from './auth/auth.service';
+import { TagController } from './tag/tag.controller';
+
+@Module({
+  imports: [],
+  controllers: [AppController, PostController, AuthController, TagController],
+  providers: [AppService, AuthService],
+})
+export class AppModule {}

+ 8 - 0
src/app.service.ts

@@ -0,0 +1,8 @@
+import { Injectable } from '@nestjs/common';
+
+@Injectable()
+export class AppService {
+  getHello(): string {
+    return 'Hello World!';
+  }
+}

+ 18 - 0
src/auth/auth.controller.spec.ts

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

+ 25 - 0
src/auth/auth.controller.ts

@@ -0,0 +1,25 @@
+import { Controller, Post, Body } from '@nestjs/common';
+import { AuthService } from './auth.service';
+
+interface AuthBody {
+  password: string;
+}
+
+@Controller('auth')
+export class AuthController {
+  constructor(private authService: AuthService) {}
+
+  @Post()
+  main(@Body() body: AuthBody) {
+    if (process.env.PASSWORD == body.password) {
+      return {
+        code: 200,
+        token: this.authService.generateBearerToken(),
+      };
+    }
+    return {
+      code: 401,
+      msg: 'Wrong password',
+    };
+  }
+}

+ 18 - 0
src/auth/auth.service.spec.ts

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

+ 32 - 0
src/auth/auth.service.ts

@@ -0,0 +1,32 @@
+import { Injectable } from '@nestjs/common';
+
+const characters =
+  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+@Injectable()
+export class AuthService {
+  private bearerToken = '';
+
+  generateBearerToken() {
+    let token = '';
+    for (let i = 0; i < 32; i++) {
+      token += characters.charAt(Math.floor(Math.random() * characters.length));
+    }
+    this.bearerToken = token;
+    return this.bearerToken;
+  }
+
+  checkBearerToken(token: string | undefined | null) {
+    if (!token) {
+      return false;
+    }
+    if (this.bearerToken === '') {
+      return false;
+    }
+    if (!token.startsWith('Bearer ')) {
+      return false;
+    }
+    token = token.substring(7);
+    return this.bearerToken === token;
+  }
+}

+ 8 - 0
src/main.ts

@@ -0,0 +1,8 @@
+import { NestFactory } from '@nestjs/core';
+import { AppModule } from './app.module';
+
+async function bootstrap() {
+  const app = await NestFactory.create(AppModule);
+  await app.listen(process.env.PORT ?? 3000);
+}
+bootstrap();

+ 18 - 0
src/post/post.controller.spec.ts

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

+ 183 - 0
src/post/post.controller.ts

@@ -0,0 +1,183 @@
+import {
+  Controller,
+  Get,
+  Query,
+  Param,
+  Post,
+  Body,
+  Headers,
+} from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+import { AuthService } from 'src/auth/auth.service';
+
+interface PostBody {
+  title: string;
+  content: string;
+  tags: string[];
+}
+
+@Controller('post')
+export class PostController {
+  constructor(private authService: AuthService) {}
+
+  @Get('/list')
+  async getAllPosts(
+    @Query('length') take: number = 10,
+    @Query('start') skip: number = 0,
+  ) {
+    const prisma = new PrismaClient();
+    return {
+      code: 200,
+      data: await prisma.post.findMany({
+        skip: Number(skip),
+        take: Number(take),
+        select: {
+          id: true,
+          title: true,
+          createdAt: true,
+          updatedAt: true,
+          tags: {
+            select: {
+              name: true,
+              id: true,
+              color: true,
+            },
+          },
+        },
+      }),
+    };
+  }
+
+  @Get('/get/:id')
+  async getPostById(@Param('id') id: string) {
+    const prisma = new PrismaClient();
+    const target = await prisma.post.findUnique({
+      where: { id: parseInt(id) },
+      select: {
+        id: true,
+        title: true,
+        content: true,
+        createdAt: true,
+        updatedAt: true,
+        tags: {
+          select: {
+            name: true,
+            id: true,
+            color: true,
+          },
+        },
+      },
+    });
+    return target
+      ? {
+          code: 200,
+          data: target,
+        }
+      : {
+          code: 404,
+          msg: 'Post not found',
+        };
+  }
+
+  @Post('push')
+  async putPost(
+    @Body() body: PostBody,
+    @Headers('Authorization') token: string,
+  ) {
+    if (!this.authService.checkBearerToken(token)) {
+      return {
+        code: 401,
+        msg: 'Unauthorized',
+      };
+    }
+    const prisma = new PrismaClient();
+    return await prisma.post
+      .create({
+        data: {
+          title: body.title,
+          content: body.content,
+          tags: {
+            connectOrCreate: body.tags.map((tag) => ({
+              where: { name: tag },
+              create: { name: tag, color: '#000000' },
+            })),
+          },
+        },
+      })
+      .then((e) => ({
+        code: 200,
+        msg: 'Post created',
+        data: {
+          id: e.id,
+        },
+      }))
+      .catch((e: Error) => ({
+        code: 500,
+        msg: e.message,
+      }));
+  }
+
+  @Post('edit/:id')
+  async editPost(
+    @Param('id') id: string,
+    @Body() body: PostBody,
+    @Headers('Authorization') token: string,
+  ) {
+    if (!this.authService.checkBearerToken(token)) {
+      return {
+        code: 401,
+        msg: 'Unauthorized',
+      };
+    }
+    const prisma = new PrismaClient();
+    return await prisma.post
+      .update({
+        where: { id: parseInt(id) },
+        data: {
+          title: body.title,
+          content: body.content,
+          tags: {
+            set: [],
+            connectOrCreate: body.tags.map((tag) => ({
+              where: { name: tag },
+              create: { name: tag, color: '#000000' },
+            })),
+          },
+        },
+      })
+      .then(() => ({
+        code: 200,
+        msg: 'Post updated',
+      }))
+      .catch((e: Error) => ({
+        code: 500,
+        msg: e.message,
+      }));
+  }
+
+  @Post('delete/:id')
+  async deletePost(
+    @Param('id') id: string,
+    @Headers('Authorization') token: string,
+  ) {
+    if (!this.authService.checkBearerToken(token)) {
+      return {
+        code: 401,
+        msg: 'Unauthorized',
+      };
+    }
+    const prisma = new PrismaClient();
+    return await prisma.post
+      .delete({
+        where: { id: parseInt(id) },
+      })
+      .then(() => ({
+        code: 200,
+        msg: 'Post deleted',
+      }))
+      .catch((e: Error) => ({
+        code: 500,
+        msg: e.message,
+      }));
+  }
+}

+ 18 - 0
src/tag/tag.controller.spec.ts

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

+ 83 - 0
src/tag/tag.controller.ts

@@ -0,0 +1,83 @@
+import { Controller, Get, Param, Headers } from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+import { AuthService } from 'src/auth/auth.service';
+
+@Controller('tag')
+export class TagController {
+  constructor(private authService: AuthService) {}
+
+  @Get('/list')
+  async getAllTags() {
+    const prisma = new PrismaClient();
+    return {
+      code: 200,
+      data: await prisma.tag.findMany({
+        select: {
+          id: true,
+          name: true,
+          color: true,
+        },
+      }),
+    };
+  }
+
+  @Get('/get/:id')
+  async getTagById(@Param('id') id: string) {
+    const prisma = new PrismaClient();
+    const target = await prisma.tag.findUnique({
+      where: { id: parseInt(id) },
+      select: {
+        id: true,
+        name: true,
+        color: true,
+        posts: {
+          select: {
+            id: true,
+            title: true,
+            createdAt: true,
+            updatedAt: true,
+          },
+        },
+      },
+    });
+    if (!target) {
+      return {
+        code: 404,
+        msg: 'Tag not found',
+      };
+    } else {
+      return {
+        code: 200,
+        data: target,
+      };
+    }
+  }
+
+  @Get('/clear')
+  async clearEmptyTags(@Headers('Authorization') token: string) {
+    if (!this.authService.checkBearerToken(token)) {
+      return {
+        code: 401,
+        msg: 'Unauthorized',
+      };
+    }
+    const prisma = new PrismaClient();
+    return await prisma.tag
+      .deleteMany({
+        where: {
+          posts: {
+            none: {},
+          },
+        },
+      })
+      .then((e) => ({
+        code: 200,
+        msg: 'Tags cleared',
+        data: e,
+      }))
+      .catch((e: Error) => ({
+        code: 500,
+        msg: e.message,
+      }));
+  }
+}

+ 25 - 0
test/app.e2e-spec.ts

@@ -0,0 +1,25 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { INestApplication } from '@nestjs/common';
+import * as request from 'supertest';
+import { App } from 'supertest/types';
+import { AppModule } from './../src/app.module';
+
+describe('AppController (e2e)', () => {
+  let app: INestApplication<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!');
+  });
+});

+ 9 - 0
test/jest-e2e.json

@@ -0,0 +1,9 @@
+{
+  "moduleFileExtensions": ["js", "json", "ts"],
+  "rootDir": ".",
+  "testEnvironment": "node",
+  "testRegex": ".e2e-spec.ts$",
+  "transform": {
+    "^.+\\.(t|j)s$": "ts-jest"
+  }
+}

+ 4 - 0
tsconfig.build.json

@@ -0,0 +1,4 @@
+{
+  "extends": "./tsconfig.json",
+  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+}

+ 21 - 0
tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "declaration": true,
+    "removeComments": true,
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "allowSyntheticDefaultImports": true,
+    "target": "ES2023",
+    "sourceMap": true,
+    "outDir": "./dist",
+    "baseUrl": "./",
+    "incremental": true,
+    "skipLibCheck": true,
+    "strictNullChecks": true,
+    "forceConsistentCasingInFileNames": true,
+    "noImplicitAny": false,
+    "strictBindCallApply": false,
+    "noFallthroughCasesInSwitch": false
+  }
+}