Skip to main content

build-app-on-azure-sql

Sequences standing up a new application on Azure SQL Database and routes each decision to the skill that owns it. Use at the start of a project, when a user says "build an app on Azure SQL", "add Azure SQL Database to my app", "which stack should I use with Azure SQL", "where do I start", "generate an API over my database", or "get this into Azure without a password in the repo". It establishes the order that works: server-side prerequisites before any code, an identity instead of a password, schema through a migration not at startup, and a generated data API before hand-written CRUD. It routes rather than repeats: provision-azure-sql-db creates the database, connect-to-azure-sql drivers and retry, entra-id-auth the identity, dab-rest-and-graphql and azure-functions-sql-bindings the API layer, deploy-app-to-azure shipping, azuresql-db-scaffold the local container version.

Datos de origen

Repositorio
microsoft/microsoft-sql
Última actividad en el origen
25 de septiembre de 2026 a las 19:53
Idioma detectado de SKILL.md
inglés
Estrellas
15
Forks
3

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
build-app-on-azure-sql
description
Sequences standing up a new application on Azure SQL Database and routes each decision to the skill that owns it. Use at the start of a project, when a user says "build an app on Azure SQL", "add Azure SQL Database to my app", "which stack should I use with Azure SQL", "where do I start", "generate an API over my database", or "get this into Azure without a password in the repo". It establishes the order that works: server-side prerequisites before any code, an identity instead of a password, schema through a migration not at startup, and a generated data API before hand-written CRUD. It routes rather than repeats: provision-azure-sql-db creates the database, connect-to-azure-sql drivers and retry, entra-id-auth the identity, dab-rest-and-graphql and azure-functions-sql-bindings the API layer, deploy-app-to-azure shipping, azuresql-db-scaffold the local container version.
# Build an application on Azure SQL Database **This is a router, not a tutorial.** It owns the order the work happens in and the four decisions that get made wrong at the start. Every technique it names belongs to another skill, and the handoff is the point. Verified 2026-09-03 against Azure CLI 2.90.0, go-sqlcmd 1.10.0, Data API builder 2.0.9 and .NET SDK 8.0.421, and against the four first-party Azure SQL Database quickstarts on Microsoft Learn. The two sqlcmd builds were compared again on 2026-09-05, go-sqlcmd 1.10.0 against ODBC sqlcmd 18.6.0002.1. ## The failure this exists to prevent Asked to build an application on Azure SQL Database, an agent starts inside the application: it picks a stack, writes a data layer, and puts a user name and password in configuration. **Every first-party Azure SQL Database quickstart starts outside the application.** All four open with the same section, before a line of code, and it configures the server rather than the project. The three things that section does are the three things that fail silently: | Prerequisite | How it fails | |---|---| | A firewall rule covering the client address | Learn documents a latency of **up to** five minutes on security setting changes. **Measured 2026-09-05 it was immediate**, so do not plan around a five minute wait and do not read a refusal as a rule that has not landed yet: see [how long a rule really takes](#how-long-a-firewall-rule-really-takes). Network address translation also means the address the client connects from is often not the one in its own network configuration | | A Microsoft Entra administrator on the logical server | Passwordless auth is simply off. The failure arrives at connect time as a login failure, which reads as a wrong credential | | A database user for the deployed identity | Nothing at deploy time reports it missing. The application returns a server error, and its code is correct | None of these is a code bug, and all three present as one. ## Step 0: prove the prerequisites, then write code Both commands are read-only. Creating the server is `provision-azure-sql-db` and configuring the identity is `entra-id-auth`. ```bash az sql server firewall-rule list -g <resource-group> -s <server> -o table az sql server ad-admin list -g <resource-group> -s <server> -o table ``` Then take one round trip to the database before an application exists, so that every later failure is a failure of the code: ```bash az login sqlcmd -S <server>.database.windows.net,1433 -d <database> -G -N mandatory -l 30 -m-1 \ -Q "SELECT SUSER_NAME() AS connected_as, DB_NAME() AS db" ``` `-G` is Microsoft Entra authentication and takes no `-U`. Open `connect-to-azure-sql` when that command fails, or before turning it into a connection string: it owns the switch-to-keyword mapping, encryption, retry and pool sizing. ### Which sqlcmd you have decides what you see Two different programs are called `sqlcmd`, and the message numbers in this skill and in every skill it routes to come out of only one of them. Microsoft Learn calls them [the two variants](https://learn.microsoft.com/sql/tools/sqlcmd/sqlcmd-utility#sqlcmd-variants): | | ODBC `sqlcmd` | Go `sqlcmd`, also written go-sqlcmd | |---|---|---| | Where it comes from | the Microsoft command line utilities, `mssql-tools18`, and the Azure SQL Database container image | a standalone download, `winget install sqlcmd`, `brew install sqlcmd` | | Version it reports | 18.x | 1.x | | How to tell | `sqlcmd -?` starts `Microsoft (R) SQL Server Command Line Tool` | `sqlcmd --version` prints a version alone | `-m-1` is an **ODBC `sqlcmd`** instruction, and it is the flag that makes a severity 10 message print its `Msg` number, which that build otherwise does not do. Measured 2026-09-05 on 18.6.0002.1: `RAISERROR('a severity ten note', 10, 1)` prints `Msg 50000, Level 0, State 1` at `-m-1` and the bare text without it. **go-sqlcmd 1.10.0 does not do this.** Measured the same day against the same engine, it prints the `Msg` header only at severity 11 and above, and a severity 10 message arrives as text with no number at `-m-1`, `-m0` and `-m1` alike, on stdout and on stderr. Its `-m` still filters, so `-m17` drops a severity 16 message entirely, but no value of it puts a number back on a warning. If the numbers a skill tells you to look for never appear, check which build you are running before you conclude the engine stayed quiet: query `sys.messages`, or run the statement through the ODBC build. Neither build turns a severity 10 message into a non-zero exit. `-b` returns 1 only above severity 10, so a warning never fails the command on either. Two facts decide what to do when the rule looks right and the connection still fails: - The five minute latency is a **cache**, and it is a ceiling rather than a wait to plan around. `DBCC FLUSHAUTHCACHE` on the user database forces a refresh rather than waiting. It needs the admin account or `KILL DATABASE CONNECTION`, and it needs a connection that already works, so it cannot rescue the connection the firewall is refusing. - A rule from `0.0.0.0` to `0.0.0.0` is the documented special case meaning Azure-internal traffic, not a wildcard for the internet, and any virtual machine in Azure may then attempt to connect. `deploy-app-to-azure` owns what the first-party templates put there. ### How long a firewall rule really takes Learn says security setting changes take **up to** five minutes. Up to is the whole sentence, and this skill used to repeat the ceiling as the expected case and tell readers the first run would fail because of it. It does not. Measured 2026-09-05 against a Basic tier database on a logical server in `eastus2`, over three trials in both directions. Each trial deleted the rule admitting the client and polled until the connection was refused, then created it again and polled until the connection succeeded, with the clock started the instant the `az` control-plane call returned and a poll every two seconds. | Trial | Rule removed, until refused | Rule created, until admitted | |---|---|---| | 1 | 1.0 s | 0.8 s | | 2 | 0.8 s | 0.9 s | | 3 | 0.8 s | 0.9 s | Every poll in every trial found the change already in effect on the **first** attempt, so the real figure is below the measurement floor: one Entra `sqlcmd` login, about 0.8 s. Read the table as "not more than a second", not as "about a second". What this changes in practice. **A connection refused straight after `az sql server firewall-rule create` returned is very unlikely to be a rule that has not propagated yet, so do not wait five minutes and try again.** Check the address first: network address translation means the address the server sees is often not the one the client believes it has, and the refusal message names the address the server saw. ```output Cannot open server 'example' requested by the login. Client with IP address '203.0.113.7' is not allowed to access the server. ... It may take up to five minutes for this change to take effect. ``` That trailing sentence is part of the server's own message and is where the expectation comes from. It is a ceiling in the message too. **One trap worth knowing before you diagnose a firewall at all.** On a server with Entra-only authentication, a SQL authentication attempt is refused for being SQL authentication, before the firewall is consulted: ```output Login failed for user 'someone'. Reason: Azure Active Directory only authentication is enabled. ``` That message appears whether the address is allowed or not, so a SQL-auth connection test cannot tell you anything about the firewall on such a server. Test with the Entra login the application will actually use. The third prerequisite cannot be checked until something is deployed, so it is in **Check it worked** below. ## The four decisions, and where each one goes A good answer names the decision, states the usual right answer, and hands off. ### 1. What does the application develop against? **Usually a real Azure SQL Database.** The free offer runs on General Purpose serverless and is sized for exactly this. Developing against a different engine and deploying to this one is how edition, limit and error-semantics differences arrive late. - Create it: `provision-azure-sql-db` - Offline or fast-teardown parity instead: `azuresql-db-scaffold` and `dev-container-templates` - On serverless, the first connection to a paused database is **expected** to fail while it resumes: `connect-to-azure-sql` owns that ### 2. How does the application authenticate? **Passwordless, in both places, with one connection string whose only difference is the auth mode.** The application code does not change between a developer's machine and Azure. What changes is which credential the identity library discovers. The deployed string Microsoft Learn publishes for the Python quickstart carries neither a user name nor a password: ```text Server=<server>.database.windows.net;Database=<database>;Authentication=ActiveDirectoryMSI;Encrypt=yes;TrustServerCertificate=no; ``` **Do not write that keyword from memory for a different driver.** Every stack spells it differently, one of them has no default mode at all, and a plausible guess produces a string the driver rejects. `entra-id-auth` carries the accepted value per driver: open it before writing the auth mode, not after the login fails. - Installation, syntax and pooling per stack: `connect-from-dotnet`, `connect-from-python`, `connect-from-typescript-and-node` - An error number that needs a cause: `diagnose-connection-errors` ### 3. Who writes the data layer? This is the decision most likely to be answered by reflex, and hand-written CRUD is usually the wrong reflex. | The application needs | Usual answer | Skill | |---|---|---| | REST or GraphQL over tables and views that already exist | Generate it. Do not hand-write controllers | `dab-rest-and-graphql` | | Serverless endpoints, or code that runs when a row changes | Input, output and trigger bindings | `azure-functions-sql-bindings` | | Rich domain objects and a mapped model | An ORM, chosen for the language | `ef-core-azure-sql`, `prisma-azure-sql`, `sqlalchemy-azure-sql`, or a supported Django SQL Server backend | | A few queries with full control over the SQL | The driver directly | the three `connect-from-*` skills | Generated, over a schema that already exists: ```bash dab init --database-type mssql --connection-string "@env('SQL_CONNECTION_STRING')" dab add Book --source dbo.books --source.type table --permissions "authenticated:read" ``` Open `dab-rest-and-graphql` before starting that engine rather than after: version 2.0 writes an `Unauthenticated` provider and serves an MCP endpoint over the same entities, so a short configuration can publish the whole database anonymously while validation passes. Hand-written, on .NET, from an empty directory: ```bash dotnet new web -o <app-name> cd <app-name> dotnet add package Microsoft.Data.SqlClient ``` `ef-core-azure-sql` owns Entity Framework Core, including which provider call to configure it with. For Prisma, pin the major: the connector for this database is a Prisma 7 feature, and an unpinned install can resolve to a next-major prerelease that has none. ```bash npm install --save-dev prisma@7 @prisma/client@7 ./node_modules/.bin/prisma migrate deploy ``` Whatever the answer, values reach the database as parameters. `prevent-sql-injection` owns that, and the Python quickstart carries an explicit warning against shipping its raw statements. ### 4. How does the schema get there? **Through a migration or a schema project, run as its own step.** Not at application startup. This needs saying out loud because the sample code contradicts it. Three of the four quickstarts create their table during startup and label it, in the sample itself, as testing only: "Table should be created ahead of time in production app". An agent that has absorbed those samples reproduces the startup DDL and drops the label. - The tool-neutral doctrine, including why startup migration is wrong: `schema-migrations-safely` - Getting the model right before it is expensive to change: `design-azure-sql-schema` ## Getting it to Azure `deploy-app-to-azure` owns the deployment itself. One thing belongs in the plan before it starts. **The identity grant is a real step and it is easy to skip.** Connecting a hosted application to the database is three operations: enable a managed identity, create a database user for it, grant it roles. One command does all three, and it needs an extension: ```bash az extension add --name serviceconnector-passwordless --upgrade az webapp connection create sql \ -g <app-resource-group> -n <app-name> \ --tg <server-resource-group> --server <server> --database <database> \ --system-identity --client-type dotnet ``` `--client-type` takes one of a closed list that includes `dotnet`, `nodejs`, `python` and `django`. Behind that one command it also **sets the Microsoft Entra administrator to the signed-in user**, opens a temporary firewall rule if the local address is blocked and deletes it afterwards, and writes `AZURE_SQL_CONNECTIONSTRING` onto the application. Changing the administrator changes a shared server, so say so before running this anywhere but a sandbox. **Check what it granted.** The documented grant is `GRANT CONTROL ON DATABASE::"<database>" TO "<user>"`, which Microsoft's own wording invites you to revoke and adjust. The quickstarts grant `db_datareader` and `db_datawriter` to the same identity instead. Replace either broad grant with a custom role containing only the permissions the application actually uses. ## Check it worked Three checks, in the order the failures arrive. **1. The prerequisites are real, not assumed.** Both commands under Step 0 return a row. An empty firewall table with a working connection means you are connecting from inside Azure, not that the rule is unnecessary. The `sqlcmd` round trip returns your Entra identity in `connected_as` and the user database, never `master`, in `db`. **2. The deployed application holds a connection string with no secret in it.** ```bash az webapp config appsettings list -g <app-resource-group> -n <app-name> \ --query "[?name=='AZURE_SQL_CONNECTIONSTRING'].value" -o tsv ``` Expect one line carrying `Authentication=` and carrying neither `Password=` nor `User ID=`. Empty output means the connection was never created, which is exactly the failure that presents as a server error from correct code. **3. The identity has a database user, and holds the roles you named.** Run the two queries in `entra-id-auth`'s own check section against the user database. A `CONTROL` or `db_owner` grant nobody asked for is the usual leftover of the one-command tool above. ## Read the source when - **A project is about to be generated**: the connect-and-query quickstart for that language on Microsoft Learn, which is the only current statement of the framework and package list. There is none for Next.js, and no driver runs in an edge runtime because it speaks a TCP protocol. Django is supported, through the Microsoft-maintained `mssql-django` backend. - **A firewall or address question is open**: the Azure SQL Database IP firewall rules article, which carries the latency table and the auth cache refresh. - **The identity will not authenticate**: the Microsoft Entra authentication configuration article, which carries the contained-user requirements and their error codes. - **The one-command connection is being considered**: the Service Connector passwordless tutorial, which is the only place the full list of what it changes is written down. ## Checklist before reporting success - [ ] Firewall rule and Microsoft Entra administrator verified, and the latency waited out or flushed - [ ] One command-line round trip succeeded before any application code was written - [ ] Connection configuration has no secret, and names the user database - [ ] The auth mode string came from `entra-id-auth`, not from memory - [ ] Schema arrives through a migration step, not through startup code - [ ] The data-layer choice was made against the routing table, not by reflex - [ ] Deployed identity has a database user with named, justified roles ## Do not - Do not write application code before the firewall rule and the Microsoft Entra administrator are confirmed. That ordering is the whole skill. - Do not read a first-run failure as a code or driver bug. Check the latency, then the admin, then the database user, in that order, and never declare a new firewall rule a success until the latency is waited out or the auth cache is flushed. - Do not put a password in configuration because passwordless "can be added later". The two paths differ in the connection string only, so there is nothing to save. - Do not copy the quickstart's startup table creation into a real application. The sample says so itself, in a comment that is easy to drop. - Do not hand-write CRUD endpoints over an existing schema without first deciding against a generated data API. - Do not leave a one-command grant unexamined. `CONTROL` on the database is not an application role. - Do not restate driver installation, retry policy, pool sizing, Data API builder configuration or binding syntax here. Every one of those has an owner, and duplicating it is how two skills start disagreeing. - Do not answer the local-container version of this question here. That is `azuresql-db-scaffold`.
Ver en GitHub