← Blog

Build AI Apps with Azure Database for PostgreSQL

2026-09-20 · 5 min read · SubToAPI Team

Azure Database for PostgreSQL is a solid backbone for AI applications because it can hold your application data, your vector embeddings, and your retrieval logic in one managed engine. You don't need a separate vector database for most workloads — Flexible Server ships with the pgvector extension, which handles similarity search directly inside the same Postgres instance you already use for users, sessions, and business data.

This guide walks through the practical steps: enabling pgvector on Azure Database for PostgreSQL, designing a schema for embeddings, running similarity queries, and wiring the whole thing to an LLM so you get a working retrieval-augmented generation (RAG) pipeline, not just a vector store with no application in front of it.

Why Azure Database for PostgreSQL for AI workloads

A few properties make it a reasonable default choice over a dedicated vector database:

The tradeoff is that pgvector isn't as fast as a purpose-built vector engine at very large scale (tens of millions of vectors with strict latency SLAs). For most product-level AI apps — internal knowledge bases, support bots, semantic search over a few hundred thousand documents — it's more than enough.

Enabling pgvector

On an existing Flexible Server instance, enable the extension through the allowlist first:

az postgres flexible-server parameter set \
  --resource-group my-rg \
  --server-name my-pg-server \
  --name azure.extensions \
  --value vector

Then, in your database:

CREATE EXTENSION IF NOT EXISTS vector;

Designing the schema

A minimal RAG schema needs a table for source documents and a table (or column) for their embeddings:

CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  metadata JSONB DEFAULT '{}',
  embedding VECTOR(1536),
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX ON documents
  USING hnsw (embedding vector_cosine_ops);

Use hnsw over ivfflat for most new projects — it doesn't require a training pass and gives better recall at similar speed. The vector dimension (1536 here) has to match whatever embedding model you use, so set it once and don't change it later without a migration.

Querying by similarity

Once documents are embedded and stored, retrieval is a single SQL query:

SELECT id, content, metadata
FROM documents
ORDER BY embedding <=> $1
LIMIT 5;

<=> is the cosine distance operator pgvector registers. You compute the query embedding in your application, pass it as $1, and Postgres returns the nearest neighbors. This is the entire "retrieval" half of RAG — no external service required.

Wiring retrieval to an LLM

The other half is generation: taking the retrieved rows and asking a model to answer using them. This is where most teams either build a custom proxy around a model provider's SDK, or use an API that already handles keys, streaming, and usage tracking. If you're already paying for Claude access and want an HTTPS endpoint you can call from a server or edge function, SubToAPI turns that into a standard API with sub_live_... keys, so the LLM call looks the same regardless of which backend serves it:

const rows = await pool.query(
  `SELECT content FROM documents ORDER BY embedding <=> $1 LIMIT 5`,
  [queryEmbedding]
);

const context = rows.rows.map(r => r.content).join("\n\n");

const res = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet",
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        content: `Answer using only this context:\n${context}\n\nQuestion: ${userQuestion}`
      }
    ]
  })
});

const data = await res.json();
console.log(data.content);

That's the full loop: Postgres retrieves relevant rows, the model generates an answer grounded in them. For chat-style apps you'll want streaming so the response appears token by token instead of after a long wait — see /docs/streaming for how that works, and /docs/messages for the full request/response shape.

Production considerations

A few things that matter once this moves past a prototype:

For teams evaluating cost, Azure Database for PostgreSQL bills for compute and storage independent of AI usage, while your model calls are billed separately by whatever provider or gateway you use. If you want a single flat rate per seat instead of managing provider billing directly, plans start at €9/month — see /pricing.

Questions

Does Azure Database for PostgreSQL support vector search out of the box? Not by default — you need to enable the pgvector extension through the server's allowlist parameter, then create it with CREATE EXTENSION vector in your database. After that, VECTOR columns and similarity queries work like any other SQL feature.

Do I need a separate vector database alongside Postgres? For most applications, no. pgvector with an hnsw index handles semantic search well up to roughly a few million vectors. Only move to a dedicated vector engine if you have strict low-latency requirements at very large scale.

How do I connect an LLM to data stored in Azure Database for PostgreSQL? Retrieve the relevant rows with a similarity query, insert that text into your prompt as context, then call your model provider's API. Tools like /docs/quickstart show how to send that context-augmented prompt to a model over a simple HTTPS request.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →