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.