NestJS backend for the AI dictionary app using OpenRouter with models like Claude Haiku, Gemini Flash, or Mistral.
🔧 Project Setup (NestJS + OpenRouter (create account and select free model)
In my project I use Gemini 2.0 ( google/gemma-3-27b-it:free )
1. Scaffold NestJS Project
nest new ai-dictionary-backend cd ai-dictionary-backend npm install axios dotenv class-validator @nestjs/config
2. Configure .env
Create a .env
file:
OPENROUTER_API_KEY=your_openrouter_key_here OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 DEFAULT_MODEL=google/gemma-3-27b-it:free # or gemini, mistral, etc.
Get an API key from OpenRouter.ai.
3. OpenRouter Module
ai/openrouter.service.ts
import { Injectable } from '@nestjs/common'; import {ConfigService} from "@nestjs/config"; import {systemPrompt} from "../constant/system-prompt"; import OpenAI from 'openai'; @Injectable() export class OpenRouterService { private readonly baseUrl: string; private readonly apiKey: string; private readonly defaultModel: string; private openai: OpenAI; constructor(private configService: ConfigService) { this.baseUrl = this.configService.get<string>('OPENROUTER_BASE_URL'); this.apiKey = this.configService.get<string>('OPENROUTER_API_KEY'); this.defaultModel = this.configService.get<string>('DEFAULT_MODEL'); this.openai = new OpenAI({ baseURL: this.baseUrl, apiKey: this.apiKey, }); } async getExplanation(prompt: string): Promise<string>{ const completion = await this.openai.chat.completions.create({ model: this.defaultModel, messages: [ { "role": "system", "content": [ { "type": "text", "text": systemPrompt }, ] }, { role: 'user', content: prompt, }, ], },); return completion.choices[0].message.content; } }
4. Phrase Controller & Service
phrase/phrase.controller.ts
import { Controller, Post, Body } from '@nestjs/common'; import { PhraseService } from './phrase.service'; @Controller('phrase') export class PhraseController { constructor(private readonly phraseService: PhraseService) {} @Post('explain') async explain(@Body('text') text: string) { return this.phraseService.explainPhrase(text); } }
phrase/phrase.service.ts
import { Injectable } from '@nestjs/common'; import {OpenRouterService} from "../openrouter/open-router.service"; import {FlashcardFormatterService} from "../flashcard/flashcard-formatter.service"; @Injectable() export class PhraseService { constructor(private readonly openRouterService: OpenRouterService, private readonly flashCardFormatter: FlashcardFormatterService,) { } async explainPhrase(phrase:string) { const result = await this.openRouterService.getExplanation(phrase); return this.flashCardFormatter.format(result); } }
5. AppModule Setup
app.module.ts
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { PhraseController } from './phrase/phrase.controller'; import { PhraseService } from './phrase/phrase.service'; import { OpenRouterService } from './ai/openrouter.service'; @Module({ imports: [ConfigModule.forRoot({ isGlobal: true })], controllers: [PhraseController], providers: [PhraseService, OpenRouterService], }) export class AppModule {}
6. Test the API
Run the app:
npm run start:dev
Test with POST /phrase/explain
using curl
, Postman, or Next.js frontend:
curl -X POST http://localhost:3000/phrase/explain \ -H "Content-Type: application/json" \ -d '{"text": "are bound to come up"}'
here the result:
✅ Next Steps (After Build Frontend)
- [ ] Add caching with MongoDB
- [ ] Store previous explanations for re-use
- [ ] Add vector search later for semantic lookup
- [ ] Rate limit per user/IP if deployed
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.