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.