How to Integrate Generative AI in Flutter Using a NestJS Backend with Gemini

Many tutorials show how to “use AI in Flutter” by calling an API directly from the app. That approach works for prototypes, but in real production environments, it brings several problems: exposed API keys, no rate limiting, no control over costs, and no ability to enforce business rules or version models over time.
A better architecture — the one I actually use in my own projects — is:
Flutter → NestJS backend → Gemini API
This gives you security, observability, and full control of your AI layer.
In this article, I will walk you through the whole architecture and provide complete NestJS + Flutter code you can copy and use in your own apps.
Why You Should NOT Call Gemini Directly from Flutter
Before we start, you might be wondering why it's not a good idea to use the Gemini API key directly in your Flutter applications.
Calling Gemini (or any AI provider) straight from the mobile client creates several critical problems:
Your API key will be exposed in case anyone reverse engineers your app.
No rate limiting to protect your bill.
No control over quotas, logs, or usage patterns.
No way to enforce business rules (max prompt size, allowed features, etc.).
You must ship new app versions if you want to change models or parameters.
Just to mention some reasons, this is why the correct architecture always includes a backend. NestJS sits between your Flutter app and Gemini, handling authentication, validation, logging, safeguards, rate limiting, and model management — while keeping your API keys 100% secure.
High-Level Architecture

NestJS becomes the “AI gateway” for all your apps — secure, flexible, and future-proof.
Before we start, please make sure you get a Gemini API key. You can get one from this page: https://aistudio.google.com/api-keys
Remember to create a Google Cloud Project before creating a new API Key.
1. Creating an AI Module in NestJS
Inside your NestJS project, we're gonna create a new module using NestJS CLI. On your terminal, put the following:
nest g module gemini
This will give us a new Module called Gemini.
src/gemini/gemini.module.ts
src/gemini/gemini.service.ts
src/gemini/gemini.controller.ts
NestJS CLI is a really cool tool that lets you create NestJS code really fast. This is one of the features I love most about Nest.
There are three key files: the Module, the Service, and the Controller. The Module acts as the orchestrator, wiring the controllers and providers for this Gemini Feature. The Gemini Controller communicates with the Gemini API through a use case that we will implement later on. And finally, the Gemini Service exposes the endpoints that our Flutter application will interact with.
gemini.module.ts
import { Module } from '@nestjs/common';
import { GeminiService } from './gemini.service';
import { GeminiController } from './gemini.controller';
@Module({
controllers: [GeminiController],
providers: [GeminiService],
})
export class GeminiModule {}
2. NestJS Service to Call Gemini
We're gona use @google/genai package. You can read the documentation in here for more information: https://ai.google.dev/gemini-api/docs For a better structure, we are gonna create a use case file as well. We have the following:
gemini.service.ts
import { Injectable } from '@nestjs/common';
import { GoogleGenAI } from '@google/genai';
import { geminiUseCase } from './use-cases/gemini.use-case';
import { GeminiPromptDto } from './dtos/gemini-promot.dto';
@Injectable()
export class GeminiService {
private ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
async geminiPrompt(geminiPromptDto: GeminiPromptDto) {
return geminiUseCase(this.ai, geminiPromptDto);
}
}
gemini.use-case.ts
import { GoogleGenAI } from '@google/genai';
import { GeminiPromptDto } from '../dtos/gemini-promot.dto';
export const geminiUseCase = async (
ai: GoogleGenAI,
basicPromptDto: GeminiPromptDto,
) => {
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: basicPromptDto.prompt,
config: {
systemInstruction: `
Responde únicamente en español
En formato markdown
Usa negritas de esta forma __
Usa el sistema métrico decimal
`,
},
});
return response.text;
};
Add your key to .env :
GEMINI_API_KEY=your_key_here
3. Exposing a Clean Endpoint for Flutter
gemini.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { GeminiService } from './gemini.service';
import { GeminiPromptDto } from './dtos/gemini-promot.dto';
@Controller('gemini')
export class GeminiController {
constructor(private readonly geminiService: GeminiService) {}
@Post('generate')
basicPrompt(@Body() geminiPromptDto: GeminiPromptDto) {
return this.geminiService.geminiPrompt(geminiPromptDto);
}
}
Now you have:
POST /gemini/generate
{
"prompt": "Write a product description for a tip calculator app."
}
4. Recommend Protections (Very Important in Production)
To operate AI safely:
Rate limiting:
import { Throttle } from '@nestjs/throttler';
@Throttle(5, 60) // 5 requests per minute
Validation Pipe
Prevent sending empty or malicious prompts.
API key or auth in your backend
Even a simple auth token adds a security layer.
Logging + token usage
Helps detect abuse and control monthly billing.
5. Calling NestJS from Flutter Using Dio
Here's a clean service class in Flutter:
ai_service.dart
import 'package:dio/dio.dart';
class AiService {
final dio = Dio(BaseOptions(baseUrl: 'https://your-backend.com'));
Future<String> generate(String prompt) async {
try {
final body = {'prompt': prompt};
final response = await _dio.post('/gemini/generate', data: jsonEncode(body));
return response.data;
} catch (e) {
return 'Error: $e';
}
}
6. Simple Flutter UI Example
chat_screen.dart
import 'package:flutter/material.dart';
import 'ai_service.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final controller = TextEditingController();
final ai = AiService();
String output = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Gemini + Flutter')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(controller: controller),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
final text = await ai.generate(controller.text);
setState(() {
output = text;
});
},
child: const Text('Generate'),
),
const SizedBox(height: 16),
Text(output),
],
),
),
);
}
}
7. My Own Experience Using This Structure
In one of my recent projects, I'm following this exact architecture. It allows me to:
- Auto-generate text on a UI interface
- Detect objects in user-uploaded photos using Gemini Vision
- Generate suggestions, summaries, and metadata
- Run lightweight agents for business logic
Flutter’s role is simpler: it just sends some input, whether text or an image.
NestJS handles everything else — calling Gemini safely, cleaning the data, and returning structured results.
The app feels lighter and faster, and you don't worry about exposing keys or AI logic in the client.
8. Some Good Practices for Production AI
Here are a few things that can save you some headaches in production:
- Put a limit on how big prompts can be
- Cache common or repeated AI responses
- Track input/output tokens so you don’t get billing surprises
- Version your models as you upgrade (gemini-pro-1.5 → 2.0, etc.)
- Handle loading states properly — AI can take a moment
- Keep every model parameter and key on the server, never in the app
Conclusion
So there you have it. Using Flutter + NestJS + Gemini behind a backend isn’t just a “nice-to-have” architecture — it’s a solid, production-ready setup with real advantages:
- Your keys and logic never reach the mobile app
- You can change models or parameters instantly from your backend without deploying a new version of your app
- You control rate limits and usage because you have more control over your API usage
- One backend can power multiple apps or platforms
Thank you so much for reading this far, and let me know if you have any questions in the comment section.
Happy Coding. :D

