DEV Community

Cover image for AI Flashcard: NestJs (Basic) And Gemini (Free)
Taki
Taki

Posted on

AI Flashcard: NestJs (Basic) And Gemini (Free)

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)

Image description

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 
Enter fullscreen mode Exit fullscreen mode

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. 
Enter fullscreen mode Exit fullscreen mode

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; } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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 {} 
Enter fullscreen mode Exit fullscreen mode

6. Test the API

Run the app:

npm run start:dev 
Enter fullscreen mode Exit fullscreen mode

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"}' 
Enter fullscreen mode Exit fullscreen mode

here the result:

Image description


✅ 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.