In this tutorial, we'll use Gemini's gemini-2.5-flash model to implement two key functionalities.
- Bidirectional Translation: Translate text between English and Spanish (and vice versa).
- Text Improvement: Refine and optimize the quality of a given text.
Check the supported models from Genkit to explore other AI models you can integrate into your projects.
What is Genkit?
Google's open-source framework for integrating AI into your projects, acting as an AI wrapper with extended functionalities.
Genkit.dev
Note: Before we start with the code, it's crucial to understand that Genkit operates exclusively in server environments (such as Node.js, Cloud Functions, etc.). Currently, there are no SDKs or methods for its direct client-side implementation.
1) Create the project
We'll start by setting up our project's basic structure and installing the necessary dependencies:
mkdir ia-translate
cd ia-translate
npm init -y
npm install genkit @genkit-ai/googleai dotenv
2) Install additional dependencies
npm install cors express zod
# Install development dependencies (for TypeScript)
npm install --save-dev types/cors types/express types/express-serve-static-core types/node typescript
3) Get the Gemini API Key
- Go to Google AI Studio: aistudio.google.com
- Create API Key: On the left sidebar, look for "Get API Key"
- Copy and Paste this key into the .env file in the backend/ directory
Remember to keep your API key secure and never commit it to version control.
4) Project Structure
To organize the code, our project will follow the structure below. This reflects the separation between the backend (with Genkit) and the frontend (the user interface).
ia-translate/
├── backend/
│ ├── app.ts # Express server: defines routes and exposes Genkit flows
│ └── translateFlow.ts # Here we define the Genkit flows (e.g., `translateTextFlow`)
│ ├── .env.example # Template for sensitive environment variables (e.g., GEMINI_API_KEY)
│ ├── .env # **Create this file.** Contains the actual environment variables (DO NOT commit to Git)
│ ├── package.json
│ └── tsconfig.json # TypeScript configuration for the backend
├── frontend/
│ ├── index.html
│ ├── styles.css
│ └── translatorIA.js # Client-side JavaScript logic to interact with the backend
├── .gitignore
└── README.md
5) Genkit Initialization and Model Configuration
Inside the backend/ folder, create a new file named translateFlow.ts. In this file, we will configure and initialize Genkit, specifying the Gemini AI model (gemini-2.5-flash) we will use and connecting it to your API Key.
backend/translateFlow.ts:
import { googleAI, gemini15Flash } from "@genkit-ai/googleai"
import { genkit, z } from "genkit"
import dotenv from "dotenv"
// --- Zod Schemas for Structured Output ---
// These schemas define the expected structure of the AI model's response.
// Genkit uses these to guide the model's output and to parse/validate it.
const optionSchema = z.object({
option: z.string(),
explanation: z.string(),
})
const improveSchema = z.object({
resultList: z.array(optionSchema),
})
const translateSchema = z.object({
resultText: z.string(),
})
dotenv.config()
const ai = genkit({
plugins: [googleAI({ apiKey: process.env.GEMINI_API_KEY })],
model: googleAI.model("gemini-2.5-flash"),
})
export const translateText = ai.defineFlow(
{
name: "translateText", // Unique name for this Genkit flow
// inputSchema: Defines the expected structure of the input data for this flow.
inputSchema: z.object({
text: z.string().min(1, "Text is required"),
language: z.enum(["English", "Spanish"]),
action: z.enum(["translate", "improve"]),
}),
/*
The flow's outputSchema is implicitly handled by the return types
and the ai.generate output schema, allowing flexible return based on action.
*/
},
async (input) => {
const { text, language, action } = input
const isImprove = action === "improve"
const prompt = isImprove
? `Please improve this ${language} text:\n\n${text}\n\nReturn a list with 2 options to improve the text.`
: `Translate this to ${language}:\n\n${text}\n\nReturn ONLY the translated text. DO NOT include any additional text, explanations, or suggestions.`
// Uses Genkit's `generate` function to call the configured AI model.
const result = await ai.generate({
prompt: prompt,
config: {
// Model configuration: lower temperature for more consistent/less creative output.
temperature: isImprove ? 0.3 : 0.2,
},
/*
output: { schema: ... }: CRUCIAL for structured output.
Instructs Genkit to expect and attempt to parse the AI's response
according to the specified Zod schema (improveSchema or translateSchema).
*/
output: { schema: isImprove ? improveSchema : translateSchema },
})
const parsedOutput = result.output
if (!parsedOutput) {
throw new Error("No result received from translation flow")
}
if (isImprove) {
if (
"resultList" in parsedOutput &&
Array.isArray(parsedOutput.resultList)
) {
return parsedOutput // Returns the object `{ resultList: [...] }` if valid
}
throw new Error(
"Output for improve action did not match expected schema.",
)
} else {
if (
"resultText" in parsedOutput &&
typeof parsedOutput.resultText === "string"
) {
return parsedOutput // Returns the object `{ resultText: "..." }` if valid
}
throw new Error(
"Output for translate action did not match expected schema.",
)
}
},
)
6) Executing the Flow (Express Server)
Now, let's create the app.ts file directly in the backend/ folder. This file will serve as the entry point for our Node.js server. It will use Express to define an API route (/api/translate) that will receive requests from the frontend and pass them to our Genkit flow (translateText).
backend/app.ts:
import express from "express"
import cors from "cors"
import { translateText } from "./translateFlow.ts"
// Define an interface for the expected structure of the request body
interface TranslateRequest {
text: string
language: "English" | "Spanish"
action: "translate" | "improve"
}
const app = express()
const PORT = process.env.PORT || 3001
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(
cors({
origin: "http://localhost:3000", // Configure CORS to allow requests only from http://localhost:3000 (your frontend)
}),
)
const translateHandler = (req: express.Request, res: express.Response) => {
const { text, language, action } = req.body as TranslateRequest
if (!text || !language || !action) {
res.status(400).json({
error: "Missing required fields: text, language, and action are required",
})
return
}
if (!text.trim()) {
res.status(400).json({
error: "Text cannot be empty",
})
return
}
translateText({ text, language, action })
.then((result: any) => {
if (!result) {
throw new Error("No result received from translation flow")
}
res.status(200).json(result)
})
.catch((error: any) => {
console.error("Error in /api/translate endpoint:", error) // Log the error to the console
res
.status(500)
.json({ error: "Failed to process request", details: error.message })
})
}
app.post("/api/translateText", translateHandler)
app.get("/", (_, res: express.Response) => {
res.send("Genkit Translation API is running!")
})
app.listen(PORT, () => {
console.log(`Server listening at http://localhost:${PORT}`) // Log the server URL to the console
console.log(`Access the API at http://localhost:${PORT}/api/translate`) // Provide API endpoint access info
})
7) Compilation and Execution
If you are using TypeScript (as in this project), you first need to compile your code. Make sure you have TypeScript installed globally:
npm install -g typescript
To leverage Genkit's Dev UI (Developer User Interface), install its command-line interface (CLI) globally:
npm install -g genkit-cli
When you run genkit start, it not only starts your server but also brings up a local web interface (usually at http://localhost:4000/flows
) that allows you to interactively inspect and debug your Genkit flows.
Genkit Developer UI
Project Scripts
To simplify execution, let's add the following scripts to the package.json file within the "scripts" object:
"scripts": {
"build": "tsc",
"dev": "npx ts-node app.ts",
"start": "genkit start -- npx ts-node app.ts"
}
8) Frontend Application
Next, let's build a simple frontend using plain HTML, CSS, and JavaScript. This will be our way of interacting with the API we built using Genkit. To easily serve these static files during development, we'll use lite-server.
I've already created the full frontend project, and you'll find the complete repository link at the end of this tutorial.
To get started, navigate to your frontend folder in the terminal and run:
npm install
npm run dev
This will launch the frontend project in your browser at http://localhost:3000
.
Below is the key part of our main JavaScript file, focusing on how we interact with the Genkit API:
frontend/translatorIA.js:
document.addEventListener("DOMContentLoaded", () => {
// Function to make the API call to our backend.
async function translateTextApiCall({ text, language, action }) {
try {
const response = await fetch(state.backendUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, language, action }),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
// This 'data' object will match the Zod schemas returned by the Genkit flow.
const data = await response.json()
return data
} catch (error) {
console.error("API call error:", error)
throw new Error("Could not connect to the server or process the request.")
}
}
async function handleAction(action) {
const text = originalText.value.trim()
if (!text) return
translatedText.innerHTML =
'<span class="loading-spinner">Traduciendo...</span>' // Show loading spinner
try {
const resultObject = await translateTextApiCall({
text,
language: state.selectedLanguage,
action,
})
// Check if the result indicates an 'improve' action or contains a resultList
// as defined by our Genkit flow's output schemas.
if (action === "improve" || resultObject.resultList?.length > 0) {
renderImproveResult(resultObject)
} else {
setText(translatedText, resultObject.resultText)
}
} catch (error) {
setText(
translatedText,
error.message || "Error: Unable to process the request.",
)
}
}
})
Demo
Here's a quick demonstration of the translator in action:
You can check out the complete project on GitHub to see it in action.
That's it, everyone! This demonstrates an easy way to wrap and use AI models in your applications with Node.js and Genkit. There's much more you can create and integrate, and I'll cover more advanced topics in future posts.
References:
- 📘 Genkit Documentation
- ✍️ Editing assistance with IA (Gemini)
Top comments (0)