Domain 2 β€” Module 8 of 11 73%
18 of 28 overall
Domain 2: Secure, Optimize, and Deploy Database Solutions Free ⏱ ~15 min read

Data API Builder: REST and GraphQL from SQL

Turn your SQL database into REST and GraphQL endpoints with Data API Builder β€” configure entities, pagination, caching, stored procedures, and deploy to Azure.

APIs without writing API code

Simple explanation

Think of a restaurant menu.

You have a kitchen full of ingredients (your database). Customers want specific dishes (data). Normally, you hire a chef (backend developer) to take orders, cook, and serve. That is expensive and slow.

Data API Builder (DAB) is like an automated kitchen. You write a menu (configuration file) that says β€œthe Customers table is available as an endpoint.” DAB handles the cooking β€” it translates HTTP requests into SQL queries, serves the results as JSON, and handles pagination, filtering, and caching. No chef required.

You get both REST (simple, URL-based) and GraphQL (flexible, query-based) endpoints from the same config.

The DAB configuration file

Everything in DAB is driven by a single JSON configuration file. Here is the structure Leo Torres at SearchWave uses:

// dab-config.json (simplified structure)
// data-source: where the database lives
// runtime: REST and GraphQL global settings
// entities: each table/view/procedure exposed as an API

Initialising a config

# Install DAB CLI
dotnet tool install -g Microsoft.DataApiBuilder

# Initialise a new config for Azure SQL
dab init \
  --database-type "mssql" \
  --connection-string "@env('SQL_CONNECTION_STRING')" \
  --host-mode "Development"

This creates dab-config.json with the database connection and default settings.

Adding entities

# Expose the Products table as REST and GraphQL
dab add Product \
  --source "dbo.Products" \
  --permissions "anonymous:read" \
  --rest true \
  --graphql true

# Expose a stored procedure
dab add SearchProducts \
  --source "dbo.usp_SearchProducts" \
  --source.type "stored-procedure" \
  --source.params "searchTerm:string" \
  --permissions "authenticated:execute" \
  --rest.methods "get" \
  --graphql.operation "query"

The resulting entity configuration in JSON:

"Product": {
  "source": {
    "object": "dbo.Products",
    "type": "table"
  },
  "rest": {
    "enabled": true,
    "path": "/products"
  },
  "graphql": {
    "enabled": true,
    "type": {
      "singular": "Product",
      "plural": "Products"
    }
  },
  "permissions": [
    {
      "role": "anonymous",
      "actions": [
        {
          "action": "read"
        }
      ]
    },
    {
      "role": "authenticated",
      "actions": [
        {
          "action": "*",
          "fields": {
            "include": ["*"],
            "exclude": ["InternalCost"]
          }
        }
      ]
    }
  ]
}

REST vs GraphQL in DAB

REST vs GraphQL endpoints in Data API Builder
FeatureRESTGraphQL
URL pattern/api/products/42/graphql (single endpoint, query in body)
Field selectionReturns all fields (or use $select OData)Client specifies exact fields needed
Related dataSeparate requests per entityNested queries fetch related entities in one call
MutationsPOST, PUT, PATCH, DELETE verbsmutation keyword in query body
Stored proceduresMapped to specific HTTP methodsMapped as query or mutation operations
CachingHTTP cache headers (standard)DAB-specific caching per entity
Best forSimple CRUD, broad tooling supportComplex queries, mobile apps (minimise data transfer)

REST examples

GET  /api/products              β†’ List all products (paginated)
GET  /api/products/42           β†’ Get product with ID 42
POST /api/products              β†’ Create a new product (body = JSON)
PUT  /api/products/42           β†’ Replace product 42
PATCH /api/products/42          β†’ Update specific fields
DELETE /api/products/42         β†’ Delete product 42

# Filtering (OData syntax)
GET /api/products?$filter=price gt 100 and category eq 'Electronics'

# Sorting
GET /api/products?$orderby=price desc

# Pagination
GET /api/products?$first=10&$after=eyJpZCI6MTB9

GraphQL examples

# Query with field selection
query {
  products(filter: { price: { gt: 100 } }) {
    items {
      id
      name
      price
    }
  }
}

# Query with nested relationship
query {
  products {
    items {
      name
      category {
        name
      }
      reviews {
        items {
          rating
          comment
        }
      }
    }
  }
}

# Mutation (create)
mutation {
  createProduct(item: {
    name: "Wireless Headphones"
    price: 79.99
    categoryId: 3
  }) {
    id
    name
  }
}

Pagination, filtering, and caching

Pagination

DAB uses cursor-based pagination by default (not page numbers). Each response includes nextLink (REST) or hasNextPage with endCursor (GraphQL).

// REST response with pagination
{
  "value": [ ... ],
  "nextLink": "/api/products?$after=eyJpZCI6MjV9"
}

Why cursor-based? It is stable when data changes. Page-number pagination breaks if rows are inserted between page requests (you might skip or duplicate items).

Caching

Enable caching to reduce database load for read-heavy endpoints:

"Product": {
  "source": { "object": "dbo.Products", "type": "table" },
  "cache": {
    "enabled": true,
    "ttl-seconds": 300
  }
}

Caching works for read operations only (GET/query). Mutations always hit the database. You can set TTL (time-to-live) per entity β€” long TTL for rarely-changing reference data, short TTL for frequently-updated records.

Exam tip: When NOT to cache

Do not enable caching for entities where users expect real-time data: financial transactions, inventory counts, or order statuses. Stale cached data in these scenarios causes business errors. The exam may describe a scenario where a team enables caching and users see outdated stock levels β€” the answer is to disable caching for that entity or reduce the TTL.

Exposing stored procedures and views

Stored procedures

Stored procedures map to specific REST methods and GraphQL operation types:

"SearchProducts": {
  "source": {
    "object": "dbo.usp_SearchProducts",
    "type": "stored-procedure",
    "parameters": {
      "searchTerm": "string",
      "maxResults": "int"
    }
  },
  "rest": {
    "path": "/search-products",
    "methods": ["get"]
  },
  "graphql": {
    "operation": "query",
    "type": {
      "singular": "SearchResult",
      "plural": "SearchResults"
    }
  }
}

Calling the procedure:

REST: GET /api/search-products?searchTerm=wireless&maxResults=10
GraphQL: query { searchProducts(searchTerm: "wireless", maxResults: 10) { ... } }

Views

Views are configured identically to tables but are read-only by default. DAB recognises that views may not support INSERT/UPDATE:

"ActiveProducts": {
  "source": {
    "object": "dbo.vw_ActiveProducts",
    "type": "view",
    "key-fields": ["id"]
  }
}

The key-fields property is required for views because DAB cannot infer the primary key from the view definition.

GraphQL relationships

DAB can create GraphQL relationships between entities, allowing nested queries:

"Product": {
  "source": { "object": "dbo.Products", "type": "table" },
  "relationships": {
    "category": {
      "cardinality": "one",
      "target.entity": "Category",
      "source.fields": ["categoryId"],
      "target.fields": ["id"]
    },
    "reviews": {
      "cardinality": "many",
      "target.entity": "Review",
      "source.fields": ["id"],
      "target.fields": ["productId"]
    }
  }
}

With these relationships, a single GraphQL query can fetch a product, its category, and all its reviews in one request. REST requires three separate calls.

Scenario: Leo builds the SearchWave product API

Leo Torres at SearchWave needs to expose their product catalog, categories, and reviews as APIs for a new mobile app. Instead of building a .NET API from scratch (weeks of work), Leo configures DAB:

  1. Three entities: Product, Category, Review
  2. GraphQL relationships so the mobile app can fetch a product with category and reviews in one call
  3. Caching on Category (changes rarely) and Product (5-minute TTL), no caching on Review (users expect immediate visibility)
  4. A stored procedure for full-text search, exposed as a GraphQL query
  5. Anonymous users can read products; authenticated users can create reviews

The entire API is running in under an hour. No backend code written.

Deployment options

OptionHow It WorksBest For
Azure Static Web AppsBuilt-in database connection feature β€” DAB runs alongside your frontendJAMstack apps, SPAs with database backends
Azure Container AppsRun DAB as a container with auto-scalingProduction APIs needing scale and isolation
Azure App ServiceDeploy as a container to App ServiceTeams already using App Service
Local / self-hostedRun dab start directly on any machineDevelopment and testing

Container deployment

FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY dab-config.json /App/dab-config.json
ENV DATABASE_CONNECTION_STRING="Server=..."
ENTRYPOINT ["dab", "start", "--config", "/App/dab-config.json"]
Question

What is the difference between REST and GraphQL in DAB?

Click or press Enter to reveal answer

Answer

REST uses URL patterns with HTTP verbs (GET /api/products/42) and returns all fields. GraphQL uses a single endpoint (/graphql) where the client specifies exactly which fields and related entities to return. REST is simpler; GraphQL is more flexible for complex data needs.

Click to flip back

Question

Why does DAB use cursor-based pagination instead of page numbers?

Click or press Enter to reveal answer

Answer

Cursor-based pagination uses an opaque token (cursor) pointing to the last item returned. This is stable even when data changes between requests. Page-number pagination can skip or duplicate rows if items are inserted or deleted between page fetches.

Click to flip back

Question

Why must you specify key-fields when exposing a view in DAB?

Click or press Enter to reveal answer

Answer

DAB needs a primary key to support single-item lookups (GET /api/entity/id) and to generate correct GraphQL types. Tables have primary keys defined in their schema, but views do not. You must explicitly tell DAB which field or fields uniquely identify each row.

Click to flip back

Knowledge Check

Leo at SearchWave configures a DAB entity for the Reviews table. Users report that new reviews they submit do not appear immediately when they refresh the product page. Reviews appear after a few minutes. What is the most likely cause?

Knowledge Check

A team wants to expose a stored procedure that calculates monthly revenue. The procedure accepts a month parameter and returns aggregated sales data. How should this be configured in DAB?

Next up: Securing AI and API Endpoints β€” lock down your REST, GraphQL, and AI model endpoints with authentication, authorization, and managed identity.