Microsoft Agent Framework DAP Created: 06 May 2026 Updated: 06 May 2026

Data API Builder with SQL Server

Data API Builder (DAB) is an open-source tool from Microsoft that automatically generates secure REST and GraphQL endpoints directly from your database tables — no application code required. In this article you will see how to spin up a SQL Server instance with Docker, seed a Todos table, configure DAB, and call the resulting API from a browser in under ten minutes.

What is Data API Builder?

DAB sits between your database and your frontend (or any HTTP client). You point it at a connection string, tell it which tables or views to expose, declare who may access them, and DAB handles every CRUD operation automatically. It supports SQL Server, PostgreSQL, MySQL, Azure SQL, and Azure Cosmos DB.

Key capabilities:

  1. Auto-generated REST endpoints (/api/{Entity}) for GET, POST, PUT, PATCH, DELETE
  2. Auto-generated GraphQL endpoint (/graphql) with full query and mutation support
  3. Built-in Swagger / OpenAPI documentation at /swagger
  4. Role-based permissions — anonymous, authenticated, or custom roles
  5. Zero application code — configuration is a single JSON file

Prerequisites

  1. Docker Desktop (used to run SQL Server locally)
  2. .NET 8 SDK or newer
  3. Data API Builder CLI — installed in the next step

Step 1 — Install the DAB CLI

Install DAB as a global .NET tool:

dotnet tool install --global Microsoft.DataApiBuilder

If you already have it installed, update instead:

dotnet tool update --global Microsoft.DataApiBuilder

Verify the installation:

dotnet tool list --global

Step 2 — Start SQL Server with Docker Compose

Rather than pulling the image manually, a docker-compose.yaml file keeps the configuration repeatable. Create the file shown below in your project folder, then start the container with one command.

services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2025-latest
container_name: sqlserver2025
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Your_Strong_Password123!"
ports:
- "1433:1433"
volumes:
- sqlserver_data:/var/opt/mssql

volumes:
sqlserver_data:
docker compose up -d

Wait a few seconds for the engine to become ready, then verify it is running:

docker exec sqlserver2025 /opt/mssql-tools18/bin/sqlcmd ^
-S localhost -U sa -P "Your_Strong_Password123!" -C -Q "SELECT 1"

Step 3 — Create the Database and Table

Create the TodosDb database and the dbo.Todos table, then insert sample rows:

-- Create the database
docker exec sqlserver2025 /opt/mssql-tools18/bin/sqlcmd ^
-S localhost -U sa -P "Your_Strong_Password123!" -C ^
-Q "CREATE DATABASE TodosDb;"

-- Create the table and seed data
docker exec sqlserver2025 /opt/mssql-tools18/bin/sqlcmd ^
-S localhost -U sa -P "Your_Strong_Password123!" -C -d TodosDb ^
-Q "CREATE TABLE dbo.Todos (
Id INT PRIMARY KEY IDENTITY(1,1),
Title NVARCHAR(200) NOT NULL,
Completed BIT NOT NULL DEFAULT 0
);
INSERT INTO dbo.Todos (Title, Completed) VALUES
('Buy groceries', 0),
('Write unit tests', 0),
('Deploy to Azure', 1);"

If you prefer a SQL client (SSMS, Azure Data Studio, or DataGrip), open a connection to localhost,1433 with user sa and run the script directly.

Step 4 — Initialize the DAB Configuration

Navigate to your project folder and run dab init. The command creates a dab-config.json file that DAB reads at startup.

dab init --database-type "mssql" ^
--host-mode "Development" ^
--connection-string "Server=localhost,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;"

--host-mode Development enables the Swagger UI and relaxed error messages. Use Production when deploying to a real environment.

Step 5 — Add the Todo Entity

Register the dbo.Todos table as a DAB entity named Todo and allow all operations without authentication (suitable for a local demo):

dab add Todo --source "dbo.Todos" --permissions "anonymous:*"

The anonymous:* permission grants create, read, update, and delete to any caller. For a production setup, replace anonymous with a named role and restrict the allowed actions accordingly.

Step 6 — Start DAB

dab start

Expected output:

Successfully completed runtime initialization.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000

DAB is now running and your API is live.

Step 7 — Test the API

REST — List All Todos

Open your browser or use curl:

GET http://localhost:5000/api/Todo

Response:

{
"value": [
{ "Id": 1, "Title": "Buy groceries", "Completed": false },
{ "Id": 2, "Title": "Write unit tests", "Completed": false },
{ "Id": 3, "Title": "Deploy to Azure", "Completed": true }
]
}

REST — Create a New Todo

POST http://localhost:5000/api/Todo
Content-Type: application/json

{
"Title": "Review pull request",
"Completed": false
}

REST — Update a Todo

PATCH http://localhost:5000/api/Todo/Id/2
Content-Type: application/json

{ "Completed": true }

REST — Delete a Todo

DELETE http://localhost:5000/api/Todo/Id/4

GraphQL — Query

POST http://localhost:5000/graphql
Content-Type: application/json

{
"query": "{ todos { items { Id Title Completed } } }"
}

Swagger UI

Browse the interactive documentation at http://localhost:5000/swagger/index.html to explore and test every endpoint without writing any client code.

How the Routing Works

DAB follows a predictable URL pattern for REST:

HTTP MethodURLOperation
GET/api/TodoList all rows
GET/api/Todo/Id/1Get one row by primary key
POST/api/TodoInsert a new row
PUT/api/Todo/Id/1Replace a row
PATCH/api/Todo/Id/1Update specific fields
DELETE/api/Todo/Id/1Delete a row

Full Example — dab-config.Development.json

This is the complete DAB configuration file generated for the TodosDb project. It includes the data source connection string, REST/GraphQL/MCP runtime settings, CORS, host mode, and the Todo entity definition.

{
"$schema": "https://github.com/Azure/data-api-builder/releases/download/v1.7.93/dab.draft.schema.json",
"data-source": {
"database-type": "mssql",
"connection-string": "Server=localhost,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;",
"options": {
"set-session-context": false
}
},
"runtime": {
"rest": {
"enabled": true,
"path": "/api",
"request-body-strict": true
},
"graphql": {
"enabled": true,
"path": "/graphql",
"allow-introspection": true
},
"mcp": {
"enabled": true,
"path": "/mcp"
},
"host": {
"cors": {
"origins": [],
"allow-credentials": false
},
"authentication": {
"provider": "AppService"
},
"mode": "development"
}
},
"entities": {
"Todo": {
"source": {
"object": "dbo.Todos",
"type": "table"
},
"graphql": {
"enabled": true,
"type": {
"singular": "Todo",
"plural": "Todos"
}
},
"rest": {
"enabled": true
},
"permissions": [
{
"role": "anonymous",
"actions": [
{
"action": "*"
}
]
}
]
}
}
}

Key Concepts Summary

  1. dab init — creates dab-config.json with the database type, connection string, and host mode.
  2. dab add <EntityName> — registers a table or view as an API entity. The entity name becomes the URL segment (/api/Todo).
  3. permissions — controls who can call which operations. Roles map to Azure Static Web Apps or Entra ID identities in production; anonymous skips authentication entirely.
  4. host-mode Development — exposes Swagger UI, verbose error messages, and allows unauthenticated introspection. Switch to Production before deploying.
  5. MCP endpoint (/mcp) — Model Context Protocol support, allowing AI agents and tools to query the API schema and data automatically.

Running DAB and SQL Server Together with Docker Compose

Running DAB locally with the CLI is fine during development, but for a reproducible, shareable setup — or for a staging environment — Docker Compose is the right tool. It lets you declare both SQL Server and DAB as services in a single YAML file, wire them together in an isolated network, and start the whole stack with one command.

Why localhost Breaks Inside Docker

When DAB runs as a Docker container, localhost inside that container refers to the container itself — not to your host machine or to the SQL Server container. This is the most common reason DAB fails to connect when moved to Docker: a config that works perfectly on the host breaks the moment it is containerised.

Docker Compose automatically creates a shared network and assigns each service a DNS name equal to the service name defined in docker-compose.yaml. If the SQL Server service is called sqlserver, then the correct hostname for the connection string is sqlserver.

Using @env() to Inject the Connection String

Hardcoding a different connection string for Docker means the config breaks locally. DAB solves this cleanly with the @env('VARIABLE_NAME') syntax: anywhere a string value appears in dab-config.json, you can replace it with an env-var reference and DAB will substitute the actual value at startup.

Change the connection-string field to:

"connection-string": "@env('SQL_CONNECTION_STRING')"

Now the same config file works in every environment. You supply a different value for SQL_CONNECTION_STRING depending on where you run it:

  1. Local CLI: set the variable in your shell before calling dab start
  2. Docker Compose: set it under the environment: key of the DAB service
  3. Azure Container Apps / App Service: set it as an application setting

PowerShell (local):

$env:SQL_CONNECTION_STRING = "Server=localhost,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;"
dab start

Bash / Linux (local):

export SQL_CONNECTION_STRING="Server=localhost,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;"
dab start

The Complete docker-compose.yaml

The file below declares two services. SQL Server 2025 starts first; DAB waits until the database port is accepting connections before it starts, then receives its connection string through an environment variable that points to the SQL Server container by service name.

services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2025-latest
container_name: sqlserver2025
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Your_Strong_Password123!"
ports:
- "1433:1433"
volumes:
- sqlserver_data:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "bash -c '/dev/null && echo ready || exit 1"]
interval: 10s
timeout: 5s
retries: 15
start_period: 30s

dab:
image: mcr.microsoft.com/azure-databases/data-api-builder:latest
container_name: dab
ports:
- "5000:5000"
environment:
- ASPNETCORE_URLS=http://+:5000
- SQL_CONNECTION_STRING=Server=sqlserver,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;
volumes:
- ./dab-config.Development.json:/App/dab-config.json
depends_on:
sqlserver:
condition: service_healthy
restart: on-failure

volumes:
sqlserver_data:

Notable points in this file:

  1. healthcheck with TCP probe — instead of calling sqlcmd (whose path differs between SQL Server versions), a lightweight Bash TCP redirect (</dev/tcp/localhost/1433) checks whether the port is open. This works regardless of the SQL Server version or tool installation.
  2. depends_on: condition: service_healthy — DAB will not start until the SQL Server healthcheck passes. Without this, DAB often crashes on startup because it tries to connect before SQL Server is ready.
  3. Server=sqlserver,1433 — the hostname is sqlserver, the Docker Compose service name. Using localhost here would fail.
  4. restart: on-failure — if DAB does start before SQL Server despite the healthcheck (e.g. on a slow machine), it will automatically retry.
  5. volume mount./dab-config.Development.json:/App/dab-config.json maps the local config file into the container at the path DAB expects.

The Complete dab-config.Development.json

This is the final configuration file with the @env() substitution applied. The connection string is no longer hardcoded; it is resolved from the environment at runtime.

{
"$schema": "https://github.com/Azure/data-api-builder/releases/download/v1.7.93/dab.draft.schema.json",
"data-source": {
"database-type": "mssql",
"connection-string": "@env('SQL_CONNECTION_STRING')",
"options": {
"set-session-context": false
}
},
"runtime": {
"rest": {
"enabled": true,
"path": "/api",
"request-body-strict": true
},
"graphql": {
"enabled": true,
"path": "/graphql",
"allow-introspection": true
},
"mcp": {
"enabled": true,
"path": "/mcp"
},
"host": {
"cors": {
"origins": [],
"allow-credentials": false
},
"authentication": {
"provider": "AppService"
},
"mode": "development"
}
},
"entities": {
"Todo": {
"source": {
"object": "dbo.Todos",
"type": "table"
},
"graphql": {
"enabled": true,
"type": {
"singular": "Todo",
"plural": "Todos"
}
},
"rest": {
"enabled": true
},
"permissions": [
{
"role": "anonymous",
"actions": [
{ "action": "*" }
]
}
]
}
}
}

Starting the Stack

docker compose up -d

Docker pulls the images (first run only), creates the shared network, starts SQL Server, waits for the TCP healthcheck to pass, then starts DAB. After about 30–40 seconds on a typical machine, DAB is ready at http://localhost:5000.

Check both containers are running:

docker compose ps

Follow DAB logs in real time:

docker compose logs -f dab

Look for Successfully completed runtime initialization and Now listening on: http://[::]:5000 to confirm a successful startup.

Tear the stack down (data is preserved in the named volume):

docker compose down

Tear down and delete the SQL Server data volume too:

docker compose down -v

Using a .env File to Avoid Secrets in YAML

Putting a password directly in docker-compose.yaml is convenient for demos but should be avoided in shared or version-controlled projects. Docker Compose reads a .env file in the same directory automatically.

Create .env:

SQL_CONNECTION_STRING=Server=sqlserver,1433;Database=TodosDb;User Id=sa;Password=Your_Strong_Password123!;TrustServerCertificate=true;Encrypt=true;
MSSQL_SA_PASSWORD=Your_Strong_Password123!

Update docker-compose.yaml to reference the variable:

environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "${MSSQL_SA_PASSWORD}"

Add .env to .gitignore so the password is never committed to source control.

How All the Pieces Fit Together

LayerFile / ToolPurpose
DatabaseSQL Server 2025 containerStores the dbo.Todos table
API runtimeDAB containerExposes REST, GraphQL, and MCP endpoints
Configdab-config.Development.jsonDeclares entities, permissions, and runtime settings
Secrets.env / environment variableSupplies connection string without hardcoding it
Orchestrationdocker-compose.yamlStarts both containers, sets networking, health checks
CLI (dev only)dab init / dab addGenerates the config file during initial setup

Reference

Microsoft Learn — Quickstart: Use Data API builder with SQL

Microsoft Learn — DAB Configuration File Reference

Microsoft Learn — Run Data API builder in a Docker container


Share this lesson: