├── packages ├── .gitkeep └── proto │ └── ai.proto ├── apps ├── app │ ├── src │ │ ├── dto │ │ │ ├── index.ts │ │ │ └── analyze.dto.ts │ │ ├── main.ts │ │ ├── app.service.ts │ │ ├── app.controller.ts │ │ └── app.module.ts │ ├── test │ │ ├── jest-e2e.json │ │ └── app.e2e-spec.ts │ └── tsconfig.app.json └── ai │ ├── src │ ├── serializers │ │ ├── index.ts │ │ └── toxicity-res.serializer.ts │ ├── constants │ │ ├── module.constant.ts │ │ ├── toxicity-threshold.constant.ts │ │ └── index.ts │ ├── helpers │ │ ├── index.ts │ │ ├── response-to-analysis.helper.ts │ │ └── model-provider.helper.ts │ ├── ai.module.ts │ ├── ai.controller.ts │ ├── ai.service.ts │ └── main.ts │ ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts │ └── tsconfig.app.json ├── libs ├── types │ ├── index.ts │ └── analysis.type.ts ├── enums │ ├── index.ts │ ├── microservice-name.enum.ts │ └── package-name.enum.ts ├── utils │ ├── index.ts │ ├── bootstrap-swagger.ts │ └── app-bindings.ts └── interfaces │ ├── index.ts │ ├── ai-grpc.interface.ts │ ├── analysis.interface.ts │ └── toxicity-res.interface.ts ├── .prettierrc ├── statics ├── responses.png ├── api_screenshot.png ├── nestjs-tensorflowjs-microservice-v3.png └── illustration_line.svg ├── tsconfig.build.json ├── .env.example ├── .gitignore ├── .vscode └── launch.json ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── nest-cli.json ├── package.json └── README.md /packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/app/src/dto/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analysis.type'; 2 | -------------------------------------------------------------------------------- /libs/enums/index.ts: -------------------------------------------------------------------------------- 1 | export * from './microservice-name.enum'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /apps/ai/src/serializers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toxicity-res.serializer'; 2 | -------------------------------------------------------------------------------- /apps/ai/src/constants/module.constant.ts: -------------------------------------------------------------------------------- 1 | export const TOXICITY_MODEL = 'TOXICITY_MODEL'; 2 | -------------------------------------------------------------------------------- /apps/ai/src/constants/toxicity-threshold.constant.ts: -------------------------------------------------------------------------------- 1 | export const TOXICITY_THRESH = 0.7; 2 | -------------------------------------------------------------------------------- /libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-bindings'; 2 | export * from './bootstrap-swagger'; 3 | -------------------------------------------------------------------------------- /libs/enums/microservice-name.enum.ts: -------------------------------------------------------------------------------- 1 | export enum MicroserviceName { 2 | APP = 'APP', 3 | AI = 'AI', 4 | } 5 | -------------------------------------------------------------------------------- /apps/ai/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toxicity-threshold.constant'; 2 | export * from './module.constant'; 3 | -------------------------------------------------------------------------------- /statics/responses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armin3731/nestjs-tensorflowjs-microservice/HEAD/statics/responses.png -------------------------------------------------------------------------------- /apps/ai/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './model-provider.helper'; 2 | export * from './response-to-analysis.helper'; 3 | -------------------------------------------------------------------------------- /libs/enums/package-name.enum.ts: -------------------------------------------------------------------------------- 1 | export enum MicroPackageName { 2 | APP = 'APP_SERVICE', 3 | AI = 'AI_SERVICE', 4 | } 5 | -------------------------------------------------------------------------------- /statics/api_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armin3731/nestjs-tensorflowjs-microservice/HEAD/statics/api_screenshot.png -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /libs/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analysis.interface'; 2 | export * from './ai-grpc.interface'; 3 | export * from './toxicity-res.interface'; 4 | -------------------------------------------------------------------------------- /statics/nestjs-tensorflowjs-microservice-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armin3731/nestjs-tensorflowjs-microservice/HEAD/statics/nestjs-tensorflowjs-microservice-v3.png -------------------------------------------------------------------------------- /apps/ai/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/app/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libs/interfaces/ai-grpc.interface.ts: -------------------------------------------------------------------------------- 1 | import { Analysis } from './analysis.interface'; 2 | 3 | export interface AiGrpcService { 4 | analyze: (reqText: RequestText) => Promise; 5 | } 6 | 7 | export interface RequestText { 8 | text: string; 9 | } 10 | -------------------------------------------------------------------------------- /libs/interfaces/analysis.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Analysis { 2 | identity_attack: boolean; 3 | insult: boolean; 4 | obscene: boolean; 5 | severe_toxicity: boolean; 6 | sexual_explicit: boolean; 7 | threat: boolean; 8 | toxicity: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /apps/ai/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "outDir": "../../dist/apps/ai" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "outDir": "../../dist/apps/app" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/interfaces/toxicity-res.interface.ts: -------------------------------------------------------------------------------- 1 | //Based on tfjs-models/toxicity package 2 | export interface ToxicityResponse { 3 | label: string; 4 | results: ToxicityResult[]; 5 | } 6 | 7 | export interface ToxicityResult { 8 | probabilities: Float32Array; 9 | match: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /apps/app/src/dto/analyze.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class AnalyzeDto { 4 | @ApiProperty({ 5 | description: 'The text you want to analyze its toxicity', 6 | example: 7 | 'We are dudes on computers, moron. You are quite astonishingly stupid.', 8 | }) 9 | text: string; 10 | } 11 | -------------------------------------------------------------------------------- /apps/ai/src/ai.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AiController } from './ai.controller'; 3 | import { AiService } from './ai.service'; 4 | import { ConfigModule } from '@nestjs/config'; 5 | import { modelProvider } from './helpers'; 6 | 7 | @Module({ 8 | imports: [ConfigModule.forRoot()], 9 | controllers: [AiController], 10 | providers: [modelProvider, AiService], 11 | }) 12 | export class AiModule {} 13 | -------------------------------------------------------------------------------- /packages/proto/ai.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ai; 4 | 5 | service AiService { 6 | 7 | rpc Analyze(RequestText) returns (Analysis); 8 | } 9 | 10 | message RequestText { 11 | string text = 1; 12 | } 13 | 14 | 15 | message Analysis { 16 | bool identity_attack = 1; 17 | bool insult = 2; 18 | bool obscene = 3; 19 | bool severe_toxicity = 4; 20 | bool sexual_explicit = 5; 21 | bool threat = 6; 22 | bool toxicity = 7; 23 | } 24 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ################ 2 | # Connectivity # 3 | ################ 4 | 5 | AI_HOST=localhost 6 | AI_RPC_PORT=5001 7 | 8 | APP_HOST=localhost 9 | APP_HTTP_PORT=3000 10 | APP_RPC_PORT=5000 11 | 12 | 13 | ################ 14 | # Swagger # 15 | ################ 16 | 17 | APPLICATION_NAME = "NestJs - TensorflowJs Service" 18 | APPLICATION_VERSION = 0.0.1 19 | APPLICATION_DESCRIPTION = "A simple microservice-architecture API that uses TensorflowJs to run machine learning tasks" 20 | -------------------------------------------------------------------------------- /apps/ai/src/helpers/response-to-analysis.helper.ts: -------------------------------------------------------------------------------- 1 | import { Analysis, ToxicityResponse } from '@app/interfaces'; 2 | 3 | export const responseToAnalysis = (response: ToxicityResponse[]) => { 4 | const output: Analysis = { 5 | identity_attack: false, 6 | insult: false, 7 | obscene: false, 8 | severe_toxicity: false, 9 | sexual_explicit: false, 10 | threat: false, 11 | toxicity: false, 12 | }; 13 | response.map((txRes) => (output[txRes.label] = txRes.results.at(0).match)); 14 | return output; 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | # .env files 38 | *.env 39 | !.example.env -------------------------------------------------------------------------------- /libs/utils/bootstrap-swagger.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 3 | 4 | export const bootstrapSwagger = (app: INestApplication) => { 5 | const config = new DocumentBuilder() 6 | .setTitle(process.env.APPLICATION_NAME) 7 | .setVersion(process.env.APPLICATION_VERSION) 8 | .setDescription(process.env.APPLICATION_DESCRIPTION) 9 | .build(); 10 | const document = SwaggerModule.createDocument(app, config); 11 | SwaggerModule.setup('api', app, document, { 12 | swaggerOptions: { docExpansion: 'none' }, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /apps/ai/src/serializers/toxicity-res.serializer.ts: -------------------------------------------------------------------------------- 1 | import { Analysis } from '@app/interfaces'; 2 | import { Exclude, Expose } from 'class-transformer'; 3 | 4 | @Exclude() 5 | export class AnalysisSerializer implements Analysis { 6 | @Expose() 7 | identity_attack: boolean; 8 | 9 | @Expose() 10 | insult: boolean; 11 | 12 | @Expose() 13 | obscene: boolean; 14 | 15 | @Expose() 16 | severe_toxicity: boolean; 17 | 18 | @Expose() 19 | sexual_explicit: boolean; 20 | 21 | @Expose() 22 | threat: boolean; 23 | 24 | @Expose() 25 | toxicity: boolean; 26 | 27 | constructor(data?: Partial) { 28 | if (data) Object.assign(this, data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libs/types/analysis.type.ts: -------------------------------------------------------------------------------- 1 | import { Analysis } from '@app/interfaces'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | // Just for Swagger 5 | export class AnalysisType implements Analysis { 6 | @ApiProperty({ example: false }) 7 | identity_attack: boolean; 8 | 9 | @ApiProperty({ example: true }) 10 | insult: boolean; 11 | 12 | @ApiProperty({ example: false }) 13 | obscene: boolean; 14 | 15 | @ApiProperty({ example: false }) 16 | severe_toxicity: boolean; 17 | 18 | @ApiProperty({ example: true }) 19 | sexual_explicit: boolean; 20 | 21 | @ApiProperty({ example: false }) 22 | threat: boolean; 23 | 24 | @ApiProperty({ example: true }) 25 | toxicity: boolean; 26 | } 27 | -------------------------------------------------------------------------------- /apps/app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { APP_BINDINGS, bootstrapSwagger } from 'libs/utils'; 4 | import { MicroserviceName } from 'libs/enums'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule, { cors: true }); 8 | 9 | bootstrapSwagger(app); 10 | const bindings = APP_BINDINGS(MicroserviceName.APP); 11 | await app.listen(bindings.httpPort, bindings.host); 12 | console.log( 13 | `${MicroserviceName.APP} successfully started on port ${bindings.httpPort}, ${bindings.url}`, 14 | ); 15 | console.log(`Swagger successfully started on ${bindings.url}/api`); 16 | } 17 | bootstrap(); 18 | -------------------------------------------------------------------------------- /apps/ai/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AiModule } from './../src/ai.module'; 5 | 6 | describe('AiController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AiModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /apps/app/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/apps/ai/src/ai.service.ts", 15 | "preLaunchTask": "tsc: build - tsconfig.json", 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/**/*.js" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /libs/utils/app-bindings.ts: -------------------------------------------------------------------------------- 1 | import { getEnv } from '@fullstacksjs/toolbox'; 2 | import { MicroserviceName } from 'libs/enums/microservice-name.enum'; 3 | import { join } from 'path'; 4 | 5 | export function APP_BINDINGS(microName: MicroserviceName) { 6 | const host = getEnv(`${microName}_RPC_HOST`) || 'localhost'; 7 | const httpPort = parseInt(getEnv(`${microName}_HTTP_PORT`) || '3000'); 8 | const rpcPort = parseInt(getEnv(`${microName}_RPC_PORT`) || '5000'); 9 | const packageName = microName.toLowerCase(); 10 | const protoPath = join(__dirname, '../../../packages/proto/ai.proto'); 11 | const url = `http://${host}:${httpPort}`; 12 | const protoUrl = `${host}:${rpcPort}`; 13 | 14 | return { host, httpPort, rpcPort, url, protoUrl, packageName, protoPath }; 15 | } 16 | -------------------------------------------------------------------------------- /apps/app/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { MicroPackageName } from '@app/enums/package-name.enum'; 2 | import { AiGrpcService, Analysis, RequestText } from '@app/interfaces'; 3 | import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; 4 | import { ClientGrpc } from '@nestjs/microservices'; 5 | 6 | @Injectable() 7 | export class AppService implements OnModuleInit { 8 | public aiService: AiGrpcService; 9 | constructor(@Inject(MicroPackageName.AI) private aiClient: ClientGrpc) {} 10 | onModuleInit() { 11 | this.aiService = this.aiClient.getService('AiService'); 12 | } 13 | 14 | async analyze(text: string): Promise { 15 | const reqText: RequestText = { 16 | text, 17 | }; 18 | return this.aiService.analyze(reqText); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/ai/src/ai.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClassSerializerInterceptor, 3 | Controller, 4 | UseInterceptors, 5 | } from '@nestjs/common'; 6 | import { AiService } from './ai.service'; 7 | import { GrpcMethod, Payload } from '@nestjs/microservices'; 8 | import { Analysis, RequestText } from '@app/interfaces'; 9 | import { AnalysisSerializer } from './serializers'; 10 | @Controller() 11 | @UseInterceptors(ClassSerializerInterceptor) 12 | export class AiController { 13 | constructor(private readonly aiService: AiService) {} 14 | 15 | @GrpcMethod('AiService', 'Analyze') 16 | async analyze(@Payload() reqText: RequestText): Promise { 17 | const analyzedText: Analysis = await this.aiService.analyzeToxicity( 18 | reqText, 19 | ); 20 | return new AnalysisSerializer(analyzedText); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/ai/src/ai.service.ts: -------------------------------------------------------------------------------- 1 | import { Analysis, RequestText, ToxicityResponse } from '@app/interfaces'; 2 | import { Inject, Injectable } from '@nestjs/common'; 3 | import { TOXICITY_MODEL } from './constants'; 4 | import * as toxicity from '@tensorflow-models/toxicity'; 5 | import { responseToAnalysis } from './helpers'; 6 | 7 | @Injectable() 8 | export class AiService { 9 | private model: toxicity.ToxicityClassifier; 10 | // eslint-disable-next-line @typescript-eslint/no-empty-function 11 | constructor(@Inject(TOXICITY_MODEL) model: toxicity.ToxicityClassifier) { 12 | this.model = model; 13 | } 14 | 15 | async analyzeToxicity(reqText: RequestText): Promise { 16 | const { text } = reqText; 17 | const response: ToxicityResponse[] = await this.model.classify(text); 18 | return responseToAnalysis(response); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/ai/src/helpers/model-provider.helper.ts: -------------------------------------------------------------------------------- 1 | import * as toxicity from '@tensorflow-models/toxicity'; 2 | import { TOXICITY_MODEL, TOXICITY_THRESH } from '../constants'; 3 | import { Logger, Provider } from '@nestjs/common'; 4 | require('@tensorflow/tfjs-node'); 5 | 6 | const logger = new Logger('ModelProvider'); 7 | export const modelProvider: Provider = { 8 | provide: TOXICITY_MODEL, 9 | useFactory: async () => { 10 | logger.verbose('Loading the model...'); 11 | const model: toxicity.ToxicityClassifier = await toxicity.load( 12 | TOXICITY_THRESH, 13 | [ 14 | 'identity_attack', 15 | 'insult', 16 | 'obscene', 17 | 'severe_toxicity', 18 | 'sexual_explicit', 19 | 'threat', 20 | 'toxicity', 21 | ], 22 | ); 23 | logger.log('Model loaded successfully'); 24 | return model; 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "paths": { 21 | "@app/utils": ["libs/utils/"], 22 | "@app/utils/*": ["libs/utils/*"], 23 | "@app/enums": ["libs/enums"], 24 | "@app/enums/*": ["libs/enums/*"], 25 | "@app/interfaces": ["libs/interfaces"], 26 | "@app/interfaces/*": ["libs/interfaces/*"] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/ai/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AiModule } from './ai.module'; 3 | import { MicroserviceOptions, Transport } from '@nestjs/microservices'; 4 | import { APP_BINDINGS } from 'libs/utils'; 5 | import { MicroserviceName } from 'libs/enums'; 6 | import { Logger } from '@nestjs/common'; 7 | 8 | async function bootstrap() { 9 | const logger = new Logger('AiMicroservice'); 10 | const bindings = APP_BINDINGS(MicroserviceName.AI); 11 | const micro = await NestFactory.createMicroservice( 12 | AiModule, 13 | { 14 | transport: Transport.GRPC, 15 | options: { 16 | url: bindings.protoUrl, 17 | package: bindings.packageName, 18 | protoPath: bindings.protoPath, 19 | loader: { keepCase: true }, 20 | }, 21 | }, 22 | ); 23 | 24 | await micro.listen(); 25 | logger.debug( 26 | `${MicroserviceName.AI} microservice successfully started on port ${bindings.rpcPort}`, 27 | ); 28 | } 29 | bootstrap(); 30 | -------------------------------------------------------------------------------- /apps/app/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Post } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | import { AnalyzeDto } from './dto/analyze.dto'; 4 | import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; 5 | import { Analysis } from '@app/interfaces'; 6 | import { AnalysisType } from 'libs/types'; 7 | 8 | @Controller() 9 | @ApiTags('AI Microservice') 10 | export class AppController { 11 | constructor(private readonly appService: AppService) {} 12 | 13 | @Post('analyze_toxicity') 14 | @ApiOperation({ 15 | summary: 'Analyzing toxicity of a text', 16 | description: 17 | 'This API receives a text and sends it to another microservice that specifies if it is insulting, threatening, or ... using a pre-trained TensorflowJs model.', 18 | }) 19 | @ApiOkResponse({ 20 | description: 'The toxicity analysis of your text', 21 | type: AnalysisType, 22 | }) 23 | async analyze(@Body() data: AnalyzeDto): Promise { 24 | return this.appService.analyze(data.text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/app/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { ConfigModule } from '@nestjs/config'; 5 | import { ClientsModule, Transport } from '@nestjs/microservices'; 6 | import { APP_BINDINGS } from 'libs/utils'; 7 | import { MicroserviceName } from 'libs/enums'; 8 | import { MicroPackageName } from 'libs/enums/package-name.enum'; 9 | 10 | @Module({ 11 | imports: [ 12 | ConfigModule.forRoot(), 13 | ClientsModule.register([ 14 | { 15 | name: MicroPackageName.AI, 16 | transport: Transport.GRPC, 17 | options: { 18 | url: APP_BINDINGS(MicroserviceName.AI).protoUrl, 19 | package: APP_BINDINGS(MicroserviceName.AI).packageName, 20 | protoPath: APP_BINDINGS(MicroserviceName.AI).protoPath, 21 | loader: { keepCase: true }, 22 | }, 23 | }, 24 | ]), 25 | ], 26 | controllers: [AppController], 27 | providers: [AppService], 28 | }) 29 | export class AppModule {} 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Armin Eskandarinasab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "apps/app/src", 5 | "compilerOptions": { 6 | "deleteOutDir": true, 7 | "webpack": true, 8 | "tsConfigPath": "apps/app/tsconfig.app.json" 9 | }, 10 | "monorepo": true, 11 | "root": "apps/app", 12 | "projects": { 13 | "netjs-ai-microservice": { 14 | "type": "application", 15 | "root": "apps/app", 16 | "entryFile": "main", 17 | "sourceRoot": "apps/app/src", 18 | "compilerOptions": { 19 | "tsConfigPath": "apps/app/tsconfig.app.json" 20 | } 21 | }, 22 | "ai": { 23 | "type": "application", 24 | "root": "apps/ai", 25 | "entryFile": "main", 26 | "sourceRoot": "apps/ai/src", 27 | "compilerOptions": { 28 | "tsConfigPath": "apps/ai/tsconfig.app.json" 29 | } 30 | }, 31 | "utils": { 32 | "type": "library", 33 | "root": "libs/utils", 34 | "entryFile": "index", 35 | "sourceRoot": "libs/utils/src", 36 | "compilerOptions": { 37 | "tsConfigPath": "libs/utils/tsconfig.lib.json" 38 | } 39 | }, 40 | "enums": { 41 | "type": "library", 42 | "root": "libs/enums", 43 | "entryFile": "index", 44 | "sourceRoot": "libs/enums/src", 45 | "compilerOptions": { 46 | "tsConfigPath": "libs/enums/tsconfig.lib.json" 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netjs-ai-microservice", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/apps/app/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./apps/app/test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@fullstacksjs/toolbox": "^3.3.0", 24 | "@grpc/grpc-js": "^1.9.5", 25 | "@nestjs/common": "^9.4.3", 26 | "@nestjs/config": "^2.3.1", 27 | "@nestjs/core": "^9.0.0", 28 | "@nestjs/microservices": "^9.4.0", 29 | "@nestjs/platform-express": "^9.0.0", 30 | "@nestjs/swagger": "^6.3.0", 31 | "@tensorflow-models/toxicity": "1.2.2", 32 | "@tensorflow/tfjs-node": "2.8.6", 33 | "class-transformer": "^0.5.1", 34 | "class-validator": "^0.14.0", 35 | "dotenv": "^16.0.3", 36 | "reflect-metadata": "^0.1.13", 37 | "rxjs": "^7.2.0" 38 | }, 39 | "devDependencies": { 40 | "@nestjs/cli": "^9.0.0", 41 | "@nestjs/schematics": "^9.0.0", 42 | "@nestjs/testing": "^9.0.0", 43 | "@types/express": "^4.17.13", 44 | "@types/jest": "29.5.0", 45 | "@types/node": "18.15.11", 46 | "@types/supertest": "^2.0.11", 47 | "@typescript-eslint/eslint-plugin": "^5.0.0", 48 | "@typescript-eslint/parser": "^5.0.0", 49 | "eslint": "^8.0.1", 50 | "eslint-config-prettier": "^8.3.0", 51 | "eslint-plugin-prettier": "^4.0.0", 52 | "jest": "29.5.0", 53 | "prettier": "^2.3.2", 54 | "source-map-support": "^0.5.20", 55 | "supertest": "^6.1.3", 56 | "ts-jest": "29.0.5", 57 | "ts-loader": "^9.2.3", 58 | "ts-node": "^10.0.0", 59 | "tsconfig-paths": "4.2.0", 60 | "typescript": "^4.7.4" 61 | }, 62 | "jest": { 63 | "moduleFileExtensions": [ 64 | "js", 65 | "json", 66 | "ts" 67 | ], 68 | "rootDir": ".", 69 | "testRegex": ".*\\.spec\\.ts$", 70 | "transform": { 71 | "^.+\\.(t|j)s$": "ts-jest" 72 | }, 73 | "collectCoverageFrom": [ 74 | "**/*.(t|j)s" 75 | ], 76 | "coverageDirectory": "./coverage", 77 | "testEnvironment": "node", 78 | "roots": [ 79 | "/apps/", 80 | "/libs/" 81 | ], 82 | "moduleNameMapper": { 83 | "^@app/utils(|/.*)$": "/libs/utils/src/$1", 84 | "^@app/enums(|/.*)$": "/libs/enums/src/$1" 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Package License 2 | 3 |

4 | DeepLearning in Microservices 5 |

6 | 7 | # DeepLearning in Microservices 8 | 9 | Implementation of TensorFlowJs models in NestJs microservice architecture 10 | 11 | ## Description 12 | 13 | This project consists of two microservices. The first microservice is called **_App_** and acts as a gateway. The App receives user's _Text_ and sends it to another microservice called **_Ai_** through a _gRPC_ connection. Ai analyzes the text and returns _toxicity_ as a boolean. Also, the type of toxicity is described in six different categories: `identity_attack`, `insult`, `obscene`, `severe_toxicity`, `sexual_explicit`, and `threat`. The results are produced from a pre-trained network in [_Pre-trained TensorFlow.js models_](https://github.com/tensorflow/tfjs-models/tree/master), specifically the [_Toxicity classifier_](https://github.com/tensorflow/tfjs-models/tree/master/toxicity). You can substitute your own model with the toxicity model and use this API for any custom application. 14 | 15 | A straightforward illustration of the project is presented below: 16 | 17 |

18 | DeepLearning in Microservices 19 |

20 | 21 | ## Give it a Star! ⭐ 22 | If you liked the project and find it useful, support me by giving it a star 😉 23 | 24 | ## Installation 25 | 26 | Clone the project 27 | 28 | ```bash 29 | git clone https://github.com/armin3731/nestjs-tensorflowjs-microservice 30 | ``` 31 | 32 | Go to the project directory 33 | 34 | ```bash 35 | cd nestjs-tensorflowjs-microservice 36 | ``` 37 | 38 | Install dependencies 39 | 40 | ```bash 41 | npm install 42 | ``` 43 | 44 | ## Environment Variables 45 | 46 | To run this project, you will need to add the following environment variables to your .env file (or simply rename **.env.example** file) 47 | 48 | `AI_HOST` 49 | 50 | `AI_RPC_PORT` 51 | 52 | `APP_HOST` 53 | 54 | `APP_HTTP_PORT` 55 | 56 | `APP_RPC_PORT` 57 | 58 | ## Running the app 59 | 60 | First, you need to run the App microservice: 61 | 62 | ```bash 63 | # runs App microservice as default 64 | $ npm run start 65 | 66 | # or 67 | $ npm run start app 68 | ``` 69 | 70 | Then open another terminal and run Ai microservice: 71 | 72 | ```bash 73 | # Ai microservice 74 | $ npm run start ai 75 | ``` 76 | 77 | **Note!** Starting the AI microservice will take some seconds because it loads the model at the start. 78 | 79 | **Note!** To my dear friends from Iran. Use a tunneling system while running Ai microservice, because Google Cloud won't let you download the model. 80 | 81 | If you use **.env.example** as your ENV variables, you will see a swagger API documentation on `http://localhost:3000/api` 82 | 83 | ## API Reference 84 | 85 | #### Analyzing Toxicity 86 | 87 | ```http 88 | POST /analyze_toxicity 89 | ``` 90 | 91 | | Parameter | Type | Description | 92 | | :-------- | :------- | :--------------------------------------------------------------- | 93 | | `text` | `string` | **Required**. The Text you want to find out if it's Toxic or Not | 94 | 95 | ## Screenshots 96 | 97 | ![API Screenshot](statics/api_screenshot.png) 98 | 99 | ![API Response](statics/responses.png) 100 | 101 | ## ToDo 102 | 103 | - Caching the model to start faster 104 | - Migration to Nest v10 105 | 106 | ## Tech Stack 107 | 108 | **ML:** TensorflowJs 109 | 110 | **Server:** Node, NestJs 111 | 112 | ## License 113 | 114 | [MIT licensed](LICENSE). 115 | -------------------------------------------------------------------------------- /statics/illustration_line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | App Microservice (Gateway)RESTAPIAI Microservice(TensorflowJs)gRPC --------------------------------------------------------------------------------