DEV Community

Naveen Jose
Naveen Jose

Posted on

Exploring Genkit GenAI in Angular

The post is about building a basic application that uses Genkit Flows, Angular and Gemini 2.0 Flash.

We are creating a fun app that suggests the user a car based on the their personality.

Following Use Genkit in an Angular app tutorial (loosely) because I encountered errors building the app using the tutorial.

Introduction

Let's start creating a full-stack Angular application with AI features.

Install the Genkit CLI globally. This is an optional dependency but I recommend it anyway

npm install -g genkit-cli
Enter fullscreen mode Exit fullscreen mode

My Angular Version

Angular CLI: 20.2.0
Node: 22.17.0
Package Manager: npm 10.9.2
OS: win32 x64

Let's create the angular application

ng new genkit-ng-demo

✔ Which stylesheet format would you like to use? CSS
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? Yes
✔ Do you want to create a 'zoneless' application without zone.js? Yes
✔ Which AI tools do you want to configure with Angular best practices? None
Enter fullscreen mode Exit fullscreen mode

Now install dependencies

code ./genkit-ng-demo #open vs code

npm install genkit
npm install @genkit-ai/googleai
npm install @genkit-ai/express
Enter fullscreen mode Exit fullscreen mode

Optional: @genkit-ai/express error

Use this section only if you encounter an error, I think this will be fixed in the latest versions

When you install @genkit-ai/express you get an error,

npm error Could not resolve dependency:
npm error peer express@"^4.21.1" from > @genkit-ai/express@1.16.1
npm error node_modules/@genkit-ai/express
npm error @genkit-ai/express@"*" from the > root project

That means you have to downgrade express package to v4.21.1.
I removed package.lock.json file, the node_modules folder and added the following to package.json

"dependencies": {
  "express": "^4.21.1",
}
Enter fullscreen mode Exit fullscreen mode

Now proceed with

npm install
npm install @genkit-ai/express
Enter fullscreen mode Exit fullscreen mode

Define Genkit Flow

The ai.defineFlow is a generic function and expects a ZodObject. The documentation shows that genkit already has the ZodObject z, but I got an errors while using import { genkit, z} from 'genkit';

Install zod

npm install zod
Enter fullscreen mode Exit fullscreen mode

Create I/O schema

import z from 'zod';

export const PersonInputSchema = z.object({
  person: z.string(),
});

export const CarOutputSchema = z.object({
  car: z.string(),
});

export type PersonModel = z.infer<typeof PersonInputSchema>;
export type CarModel = z.infer<typeof CarOutputSchema>;
Enter fullscreen mode Exit fullscreen mode

Create Genkit Flow

import { googleAI } from '@genkit-ai/googleai';
import { genkit } from 'genkit';
import { z } from 'zod';
import {
  CarModel,
  CarOutputSchema,
  PersonInputSchema,
} from '../app/schemas/car.schema';

const ai = genkit({
  plugins: [googleAI()],
});

const carSuggestionFlow = ai.defineFlow(
  {
    name: 'carSuggestionFlow',
    inputSchema: PersonInputSchema,
    outputSchema: CarOutputSchema,
    streamSchema: z.string(),
  },
  async ({ person }, { sendChunk }) => {
    const { stream } = ai.generateStream({
      model: googleAI.model('gemini-2.5-flash'),
      prompt: `Make a concise (100 words), and funny car suggestion for a ${person} person.`,
    });

    const model: CarModel = { car: '' };
    for await (const chunk of stream) {
      sendChunk(chunk.text);
      model.car += chunk.text;
    }

    return model;
  }
);

const carSuggestionFlowUrl = '/api/car-suggestion';

export { carSuggestionFlow, carSuggestionFlowUrl };
Enter fullscreen mode Exit fullscreen mode

Configure server.ts route

Again, this is a lot different from the example given from the Genkit in Angular App Tutorial
The below config is the one that worked for me

import {
  AngularNodeAppEngine,
  createNodeRequestHandler,
  writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
import { join } from 'node:path';
import cors from 'cors';

import { expressHandler } from '@genkit-ai/express';
import {
  carSuggestionFlow,
  carSuggestionFlowUrl,
} from './genkit/car-suggestion-flow';

const browserDistFolder = join(import.meta.dirname, '../browser');

const app = express();

app.use(cors());
app.use(express.json());

app.post(carSuggestionFlowUrl, expressHandler(carSuggestionFlow));

app.use(
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: false,
    redirect: false,
  })
);

const angularApp = new AngularNodeAppEngine();

app.use((req, res, next) => {
  // Only handle requests that are not your API endpoints.
  if (req.originalUrl.startsWith('/api/')) {
    return next();
  }

  angularApp
    .handle(req)
    .then((response) => {
      if (!response) return next();
      return writeResponseToNodeResponse(response, res);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

const port = process.env['PORT'] || 4000;
app.listen(port, (error) => {
  if (error) {
    throw error;
  }

  console.log(`Listening on port: ${port}`);
});

export const reqHandler = createNodeRequestHandler(app);
Enter fullscreen mode Exit fullscreen mode

Calling the flow from the frontend

In your frontend code, you can now call your flows using the Genkit client library. You can use both non-streaming and streaming approaches:

Non-streaming Flow Calls

<main>
  <section class="prompt-section">
    <h3>Suggest a Car</h3>
    <label for="theme">Suggest a car for a person with the personality: </label>
    <input type="text"
           placeholder="jovial, fun loving, adventurous"
           [(ngModel)]="personInput" />
    <button (click)="generateCar()"
            [disabled]="loading">
      Generate
    </button>
  </section>
  @if (carResult) {
  <section class="result-section">
    <h4>Generated Car:</h4>
    <article [innerHTML]="carResult"></article>
  </section>
  }
</main>
Enter fullscreen mode Exit fullscreen mode
import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { runFlow } from 'genkit/beta/client';
import { marked } from 'marked';
import { CarModel, PersonModel } from './schemas/car.schema';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  imports: [FormsModule],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  private sanitizer = inject(DomSanitizer);

  loading = false;
  personInput = '';
  result: CarModel | null = null;
  carResult: SafeHtml | null = null;

  async generateCar() {
    this.clearCarResult();
    this.loading = true;
    this.result = await this.invokeCarFlow();
    if (!this.result) {
      this.result = { car: 'No car suggestion available.' };
    } else {
      const carHtml = await marked.parse(this.result.car);
      this.carResult = this.sanitizer.bypassSecurityTrustHtml(carHtml);
    }
    this.loading = false;
  }

  async invokeCarFlow() {
    const url = environment.apiBaseUrl + '/car-suggestion';
    const model: PersonModel = { person: this.personInput.trim() };
    return runFlow({
      url,
      input: model,
    });
  }

  clearCarResult() {
    this.carResult = null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Extras

  1. added npm install marked A small library that converts markdown to html. Gemini API returns the result as markdown.
  2. app.css for the sake of completeness
main {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

main>section {
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 0.5rem;
}

main>section+section {
    margin-top: 0.5rem;
}

main>section.prompt-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 35rem;
}

main>section.prompt-section>input {
    margin: 1rem 0;
    padding: 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    width: 70%;
}

main>section.prompt-section>button {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    background-color: #007bff;
    color: white;
    cursor: pointer;
}

main>section.result-section {
    min-width: 35rem;
}
Enter fullscreen mode Exit fullscreen mode

Running the App

npm run start #ng serve
Enter fullscreen mode Exit fullscreen mode

The page should load like this

Initial Screen

Now if you enter any value and press enter you will get the error

Non-streaming request failed with error: FAILED_PRECONDITION: Please pass in the API key or set the GEMINI_API_KEY or GOOGLE_API_KEY environment variable.

For more details see https://genkit.dev/docs/plugins/google-genai

GenkitError: FAILED_PRECONDITION: Please pass in the API key or set the GEMINI_API_KEY or GOOGLE_API_KEY environment variable.

As the error notes, we have to get an API key.

Getting the API key

  1. Generate an API key for the Gemini API using Google AI Studio.
  2. Set the GEMINI_API_KEY environment variable to your key:
$env:GEMINI_API_KEY = "<your API key>"
Enter fullscreen mode Exit fullscreen mode
export GEMINI_API_KEY=<your API key>
Enter fullscreen mode Exit fullscreen mode

Final Screen

Please note that you will have to again set the environment variable when you restart the session. For a more permanent solution, please add your key to environment variable.

Source Code: ng-genkit-car-suggestor

Top comments (0)