Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/firebase/genkit/llms.txt

Use this file to discover all available pages before exploring further.

Node.js Deployment

Deploy Genkit applications to any Node.js platform using Express integration. Works with Vercel, Fly.io, AWS, Render, Railway, and more.

Overview

Genkit provides first-class Express.js integration through the @genkit-ai/express plugin:
  • Framework agnostic - Works with any Node.js HTTP server
  • Built-in streaming - Server-Sent Events (SSE) support
  • Authentication - Context providers for auth
  • Flexible deployment - Deploy anywhere Node.js runs

Installation

npm install genkit @genkit-ai/express @genkit-ai/google-genai
npm install express cors body-parser
npm install --save-dev @types/express @types/cors

Basic Setup

1. Create Express Server

src/index.ts
import { expressHandler } from '@genkit-ai/express';
import { googleAI } from '@genkit-ai/google-genai';
import express from 'express';
import { genkit, z } from 'genkit';

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

// Define a flow
const jokeFlow = ai.defineFlow(
  {
    name: 'jokeFlow',
    inputSchema: z.string(),
    outputSchema: z.string(),
  },
  async (subject) => {
    const result = await ai.generate({
      model: googleAI.model('gemini-2.5-flash'),
      prompt: `Tell me a joke about ${subject}`,
    });
    return result.text;
  }
);

const app = express();
app.use(express.json());

// Expose flow via expressHandler
app.post('/joke', expressHandler(jokeFlow));

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

2. Test Locally

# Set API key
export GEMINI_API_KEY=your-api-key

# Run server
npm run dev

# Test endpoint
curl -X POST http://localhost:3000/joke \
  -H "Content-Type: application/json" \
  -d '{"data": "programming"}'

Express Handler Options

Basic Usage

import { expressHandler } from '@genkit-ai/express';

// Simple handler
app.post('/flow', expressHandler(myFlow));

With Authentication

import type { ContextProvider } from 'genkit/context';
import { UserFacingError } from 'genkit';

interface AuthContext {
  auth: {
    username: string;
  };
}

// Context provider extracts auth from request
function requireAuth(requiredUser?: string): ContextProvider<AuthContext> {
  return (req) => {
    const token = req.headers['authorization'];
    
    const context: AuthContext = {
      auth: {
        username: token === 'secret' ? 'admin' : 'guest',
      },
    };
    
    // Validate
    if (requiredUser && context.auth.username !== requiredUser) {
      throw new UserFacingError('PERMISSION_DENIED', context.auth.username);
    }
    
    return context;
  };
}

// Apply auth to flow
app.post(
  '/secure-flow',
  expressHandler(myFlow, {
    contextProvider: requireAuth('admin'),
  })
);

With Streaming

Streaming is enabled automatically when:
  1. Request has Accept: text/event-stream header, OR
  2. Request has ?stream=true query parameter
# Enable streaming with query param
curl -X POST http://localhost:3000/joke?stream=true \
  -H "Content-Type: application/json" \
  -d '{"data": "cats"}'

# Or with Accept header
curl -X POST http://localhost:3000/joke \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"data": "cats"}'
Response format:
data: {"message": "Why did the cat"}

data: {"message": " sit on the computer?"}

data: {"result": "Why did the cat sit on the computer? To keep an eye on the mouse!"}

Flow Server

Use startFlowServer to automatically expose all flows:
import { startFlowServer } from '@genkit-ai/express';

// Define flows
const jokeFlow = ai.defineFlow(/* ... */);
const summarizeFlow = ai.defineFlow(/* ... */);

// Start server with all flows
startFlowServer({
  flows: [jokeFlow, summarizeFlow],
  port: 3000,
  cors: { origin: true },
});
This exposes:
  • POST /jokeFlow
  • POST /summarizeFlow

With Authentication

import { withFlowOptions } from '@genkit-ai/express';

startFlowServer({
  flows: [
    // Flow with auth
    withFlowOptions(jokeFlow, {
      contextProvider: requireAuth('admin'),
    }),
    // Flow without auth
    summarizeFlow,
  ],
  port: 3000,
});

Custom Paths

startFlowServer({
  flows: [
    withFlowOptions(jokeFlow, {
      path: 'api/joke', // Custom path
    }),
  ],
  pathPrefix: 'v1', // Prefix all paths
  port: 3000,
});
// Results in: POST /v1/api/joke

Advanced Patterns

Direct Flow Invocation

// Call flow directly from route handler
app.get('/joke/:subject', async (req, res) => {
  try {
    const result = await jokeFlow(req.params.subject);
    res.json({ joke: result });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Raw Streaming

app.get('/stream/:subject', async (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/plain',
    'Transfer-Encoding': 'chunked',
  });

  await ai.generate({
    prompt: `Tell me a story about ${req.params.subject}`,
    model: googleAI.model('gemini-2.5-flash'),
    onChunk: (chunk) => {
      res.write(chunk.content[0].text);
    },
  });

  res.end();
});

Multiple Flows

// Automatically expose all defined flows
ai.flows.forEach((flow) => {
  app.post(`/${flow.name}`, expressHandler(flow));
});

CORS Configuration

import cors from 'cors';

app.use(cors({
  origin: 'https://yourdomain.com',
  credentials: true,
}));

Deployment Platforms

Vercel

vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "src/index.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/index.ts"
    }
  ]
}
# Deploy
vercel deploy --prod

Fly.io

fly.toml
app = "genkit-app"
primary_region = "sjc"

[build]
  builder = "heroku/buildpacks:20"

[env]
  PORT = "8080"

[[services]]
  http_checks = []
  internal_port = 8080
  protocol = "tcp"

  [[services.ports]]
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443
# Deploy
flyctl deploy

# Set secrets
flyctl secrets set GEMINI_API_KEY=your-key

Render

render.yaml
services:
  - type: web
    name: genkit-app
    env: node
    buildCommand: npm install && npm run build
    startCommand: npm start
    envVars:
      - key: GEMINI_API_KEY
        sync: false
# Deploy via dashboard or CLI
render deploy

Railway

# Install Railway CLI
npm install -g @railway/cli

# Deploy
railway login
railway init
railway up

# Set environment variables
railway variables set GEMINI_API_KEY=your-key

AWS Elastic Beanstalk

# Install EB CLI
pip install awsebcli

# Initialize
eb init -p node.js-18 genkit-app

# Create environment
eb create genkit-env

# Set environment variables
eb setenv GEMINI_API_KEY=your-key

# Deploy
eb deploy

Heroku

Procfile
web: node dist/index.js
# Create app
heroku create genkit-app

# Set config
heroku config:set GEMINI_API_KEY=your-key

# Deploy
git push heroku main

Complete Example

See the full Express integration example:
cd js/testapps/express
npm install
npm run build
npm start
Source: js/testapps/express/src/index.ts This example demonstrates:
  • Flow via expressHandler with auth
  • Flow without auth
  • Direct flow invocation
  • Raw streaming
  • Context providers

Testing the Example

# Test with auth (requires "Authorization: open sesame")
curl http://localhost:5000/jokeFlow?stream=true \
  -d '{"data": "banana"}' \
  -H "Content-Type: application/json" \
  -H "Authorization: open sesame"

# Test without auth
curl http://localhost:5000/jokeHandler?stream=true \
  -d '{"data": "banana"}' \
  -H "Content-Type: application/json"

# Test direct flow invocation
curl "http://localhost:5000/jokeWithFlow?subject=banana"

# Test raw streaming
curl "http://localhost:5000/jokeStream?subject=banana"

Durable Streaming (Beta)

Persist stream state to handle client reconnections:
import { InMemoryStreamManager } from 'genkit/beta';

const streamManager = new InMemoryStreamManager({
  ttl: 600000, // 10 minutes
});

app.post(
  '/durable-stream',
  expressHandler(myStreamingFlow, {
    streamManager,
  })
);
Clients receive a stream ID in the X-Genkit-Stream-Id header:
# Initial request
curl -X POST http://localhost:3000/durable-stream \
  -H "Accept: text/event-stream" \
  -d '{"data": "input"}' \
  -i  # Show headers

# Reconnect with stream ID
curl -X POST http://localhost:3000/durable-stream \
  -H "Accept: text/event-stream" \
  -H "X-Genkit-Stream-Id: <stream-id>" \
  -d '{"data": "input"}'

Production Best Practices

1. Error Handling

import { UserFacingError } from 'genkit';

const safeFlow = ai.defineFlow(
  { name: 'safeFlow' },
  async (input) => {
    try {
      return await ai.generate({ prompt: input });
    } catch (error) {
      console.error('Generation failed:', error);
      throw new UserFacingError(
        'INTERNAL',
        'Failed to generate response'
      );
    }
  }
);

2. Request Validation

app.post('/joke', (req, res, next) => {
  if (!req.body || !req.body.data) {
    return res.status(400).json({ error: 'Missing data field' });
  }
  next();
}, expressHandler(jokeFlow));

3. Rate Limiting

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per window
});

app.use('/api/', limiter);

4. Timeout Handling

app.use((req, res, next) => {
  req.setTimeout(300000); // 5 minutes
  res.setTimeout(300000);
  next();
});

5. Health Checks

app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: Date.now(),
  });
});

Monitoring

Express Middleware

app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
  });
  next();
});

Structured Logging

import { logger } from 'genkit/logging';

logger.setLogLevel('debug');

app.use((req, res, next) => {
  logger.info('Request received', {
    method: req.method,
    path: req.path,
    ip: req.ip,
  });
  next();
});

Troubleshooting

Request Body Undefined

Problem: request.body is undefined. Solution: Add JSON middleware:
app.use(express.json());

CORS Errors

Problem: Browser blocks requests. Solution: Enable CORS:
import cors from 'cors';
app.use(cors({ origin: true }));

Streaming Not Working

Problem: Streaming doesn’t start. Solution: Add query parameter or header:
curl -X POST http://localhost:3000/flow?stream=true ...
# OR
curl -X POST http://localhost:3000/flow \
  -H "Accept: text/event-stream" ...

Next Steps

Express Plugin

Full Express plugin documentation

Cloud Run

Deploy to Google Cloud Run

Build docs developers (and LLMs) love