CrewAI Tutorial (TypeScript): connecting to PostgreSQL for intermediate developers

By Cyprian AaronsUpdated 2026-04-21
crewaiconnecting-to-postgresql-for-intermediate-developerstypescript

This tutorial shows how to wire a CrewAI TypeScript agent to PostgreSQL so it can read structured business data and use it in tasks. You’d use this when your agent needs live customer, policy, claim, or transaction data instead of static prompts or files.

What You'll Need

  • Node.js 18+
  • A PostgreSQL database with a reachable connection string
  • A .env file for secrets
  • OPENAI_API_KEY
  • DATABASE_URL in standard Postgres URI format
  • crewai
  • pg
  • dotenv
  • TypeScript tooling: typescript, tsx, and @types/node

Install the packages:

npm init -y
npm install crewai pg dotenv
npm install -D typescript tsx @types/node

Create a basic TypeScript config:

npx tsc --init --rootDir src --outDir dist --module nodenext --target es2022 --moduleResolution nodenext --esModuleInterop true

Step-by-Step

  1. Create your environment file and make sure both the LLM key and database URL are available at runtime. Keep this out of source control; in production, inject it through your secrets manager or platform config.
cat > .env << 'EOF'
OPENAI_API_KEY=your_openai_key_here
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/insurance_db
EOF
  1. Add a small PostgreSQL helper that opens a pooled connection and exposes a typed query function. This keeps database access isolated from the agent logic and makes it easy to swap credentials or add retries later.
// src/db.ts
import 'dotenv/config';
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export async function query<T>(text: string, params: unknown[] = []) {
  const result = await pool.query<T>(text, params);
  return result.rows;
}

export async function closeDb() {
  await pool.end();
}
  1. Seed a table with realistic data you can ask the agent about. For an insurance workflow, start with claims because they’re easy to validate and useful for testing retrieval against real rows.
// src/seed.ts
import 'dotenv/config';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function main() {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS claims (
      id SERIAL PRIMARY KEY,
      claim_number TEXT UNIQUE NOT NULL,
      customer_name TEXT NOT NULL,
      status TEXT NOT NULL,
      amount NUMERIC(12,2) NOT NULL,
      updated_at TIMESTAMP DEFAULT NOW()
    )
  `);

  await pool.query(
    `INSERT INTO claims (claim_number, customer_name, status, amount)
     VALUES ($1, $2, $3, $4)
     ON CONFLICT (claim_number) DO NOTHING`,
    ['CLM-1001', 'Amina Patel', 'open', 4200.50]
  );

  await pool.end();
}

main();

Run it once:

npx tsx src/seed.ts
  1. Build a CrewAI tool that queries PostgreSQL by claim number. The important part is keeping the tool narrow: one job, one SQL statement, no free-form SQL from the model.
// src/tools.ts
import { Tool } from 'crewai';
import { query } from './db';

export const getClaimByNumber = new Tool({
  name: 'get_claim_by_number',
  description: 'Fetch a claim record from PostgreSQL using a claim number like CLM-1001.',
  func: async (claimNumber: string) => {
    const rows = await query<{
      id: number;
      claim_number: string;
      customer_name: string;
      status: string;
      amount: string;
    }>(
      `SELECT id, claim_number, customer_name, status, amount
       FROM claims
       WHERE claim_number = $1`,
      [claimNumber.trim()]
    );

    return JSON.stringify(rows[0] ?? { error: 'Claim not found' });
  },
});
  1. Create the crew and attach the tool to an agent that answers operational questions using live database data. Keep the prompt specific so the model knows it must call the tool before responding.
// src/index.ts
import 'dotenv/config';
import { Agent, Crew, Task } from 'crewai';
import { getClaimByNumber } from './tools';
import { closeDb } from './db';

const analyst = new Agent({
  name: 'Claims Analyst',
  role: 'Insurance operations analyst',
  goal: 'Answer questions about claims using PostgreSQL records',
  backstory:
    'You work with claims operations and must rely on database facts only.',
  tools: [getClaimByNumber],
});

const task = new Task({
  description:
    'Given the claim number CLM-1001, retrieve the record and summarize its status and amount.',
  expectedOutput:
    'A concise summary containing claim number, customer name, status, and amount.',
  agent: analyst,
});

async function main() {
  const crew = new Crew({
    agents: [analyst],
    tasks: [task],
  });

  const result = await crew.kickoff();
  console.log(result);
  await closeDb();
}

main();
  1. Run the agent and confirm it can reach both OpenAI and PostgreSQL. If your environment is correct, you should see a response grounded in the seeded row rather than a hallucinated answer.
npx tsx src/index.ts

Testing It

Start by checking that Postgres is reachable with your DATABASE_URL. If the seed script succeeds but the crew fails, the problem is usually API credentials or an import/runtime mismatch in TypeScript.

Then verify that the output includes CLM-1001, Amina Patel, open, and 4200.50. If you get "Claim not found", check whether your seed ran against the same database instance your agent is using.

For a stronger test, change the task description to ask for a different claim number after inserting another row. That confirms you’re not hardcoding responses anywhere in the agent path.

Next Steps

  • Add more tools for write-safe workflows like updating claim status or fetching policy details.
  • Introduce row-level access controls so each agent only sees records it’s allowed to read.
  • Wrap queries in service-layer functions with audit logging before exposing them to production crews.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides