Build and Deploy a C#/.NET API to Azure
Build a Task Manager REST API in C#/.NET 10 and deploy it to Azure App Service.
Introduction
30 Second Summary
Every app you use daily, from your to-do list to your calendar, talks to a backend API. These APIs handle storing your data, processing your requests, and sending back exactly what you need.
In this project, you will build a Task Manager REST API from scratch using C# and .NET 10. You will deploy it to the cloud so anyone can access it from a public URL.
What You'll Build
A RESTful CRUD API that manages tasks with titles, descriptions, due dates, and completion status, backed by a SQLite database and deployed to Azure App Service.
By the end of this project, you'll have:
- A working REST API built in C# using ASP.NET Core Minimal APIs.
- A SQLite database managed through Entity Framework Core with migrations.
- A live, publicly-accessible API endpoint deployed to Azure App Service using the Azure CLI.
- Secret Mission: Add filtering and search to your task endpoints using LINQ queries and query string parameters.
Are there any prerequisites?
This project assumes you are familiar with JavaScript and REST APIs. You should also have Git installed on your machine.
No prior C# experience is needed. The project teaches you C# syntax as you build.
Before We Start
Every API starts with the right tools in place. In this step, you will install the .NET 10 SDK, the Azure CLI, and the C# Dev Kit extension for VS Code.
You will also create a free Azure account, sign in from your terminal, and scaffold a brand new .NET Web API project. By the end of this step, your development environment will be fully ready to build.
In this step, get ready to:
- Install the .NET 10 SDK, Azure CLI, and C# Dev Kit extension.
- Create a free Azure account and sign in via the CLI.
- Scaffold a new .NET Web API project and open it in VS Code.
Install the .NET 10 SDK, Azure CLI, and C# Dev Kit extension
The .NET SDK is what lets you build and run C# applications from the command line. The Azure CLI is a terminal tool that lets you manage Azure cloud resources without leaving your editor.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
- Install the .NET 10 SDK by running this command:
winget install Microsoft.DotNet.SDK.10
What does this command do?
winget is Windows' built-in package manager. This command downloads and installs the .NET 10 SDK, which includes the dotnet CLI you will use throughout this project.
- Verify the installation succeeded by running:
dotnet --version
You should see a version number starting with 10. (for example, 10.0.100 or similar).
✔️ I see a version starting with 10.
The .NET 10 SDK is installed and ready. Move on to the Azure CLI install below.
ⓧ I see an older version
Your system has an older .NET SDK installed. Run the same winget command to install .NET 10 side-by-side:
winget install Microsoft.DotNet.SDK.10
Close and reopen PowerShell, then run dotnet --version again to confirm the version starts with 10. now.
ⓧ Command not found
The .NET SDK was not installed correctly. Try running the install command again:
winget install Microsoft.DotNet.SDK.10
If winget itself is not recognized, make sure you are running Windows 10 version 1809 or later with the App Installer package up to date from the Microsoft Store. After installing, close and reopen PowerShell before retrying dotnet --version.
- Install the Azure CLI by running:
winget install --exact --id Microsoft.AzureCLI
- Close and reopen PowerShell so the new az command is recognized.
- Verify the Azure CLI is working by running:
az --version
You should see azure-cli followed by a version number (for example, 2.86.0 or higher).
az not recognized?
Make sure you closed and reopened PowerShell after installing. The az command only becomes available in new terminal sessions after installation completes.
help me troubleshoot why az is not recognized in PowerShell after installing Azure CLI with winget
- Now install the C# Dev Kit extension in VS Code. Press the Windows key, type VS Code, and press Enter to open it.
- Open the Extensions panel by pressing Ctrl+Shift+X.
- Type C# Dev Kit in the search bar at the top of the panel.
- Click Install on the C# Dev Kit extension by Microsoft.
What is C# Dev Kit?
C# Dev Kit adds IntelliSense, debugging, project management, and test discovery for .NET projects inside VS Code. It gives you a full C# development experience without needing the full Visual Studio IDE.
Create a free Azure account and sign in
Azure is Microsoft's cloud platform where you will deploy your finished API. The free account gives you $200 in credit for 30 days plus access to always-free services.
- Open your browser and navigate to:
- https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account
- Click Try Azure for free and follow the sign-up steps.
- You will need a Microsoft account and a credit or debit card for identity verification.
Will I be charged?
No. A temporary $1 authorization hold may appear on your card, but you will not be charged unless you explicitly upgrade to pay-as-you-go. This entire project runs on the free tier.
- Once your account is created, return to PowerShell and sign in to the Azure CLI by running:
az login
A browser window opens asking you to sign in with your Microsoft account. After signing in, the terminal displays your subscription details in JSON format.
Browser didn't open?
If no browser window appears, the terminal will display a URL and a device code. Open the URL in your browser manually and enter the code shown in the terminal.
help me sign in to the Azure CLI when the browser window does not open automatically
Scaffold a new .NET Web API project
Now you will create the project folder that holds your entire API. The dotnet new webapi command generates a ready-to-run project structure from a template.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your new project by running:
dotnet new webapi -n TaskApi --no-openapi
What does this command do?
dotnet new webapi scaffolds a new ASP.NET Core Minimal API project. The -n TaskApi flag names the project and creates a TaskApi folder.
The --no-openapi flag turns off the default OpenAPI/Swagger code so you start with a cleaner file. You will build your endpoints from scratch instead of working around sample code.
- Move into the new project folder by running:
cd TaskApi
- Confirm the project files exist by running:
ls
You should see Program.cs, TaskApi.csproj, and other files like appsettings.json in the output.
The template generates a sample weather forecast endpoint that you do not need. Replace the contents of Program.cs with a clean starting point.
- Open the project in VS Code by running:
code .
- In the Explorer sidebar on the left, click Program.cs to open it.
- Select all the existing code with Ctrl+A and delete it.
- Replace it with this clean starting point:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();
What does this code do?
These three lines are the foundation of every ASP.NET Core app. WebApplication.CreateBuilder(args) sets up the app's configuration and services.
builder.Build() creates the application instance. app.Run() starts the web server and listens for incoming HTTP requests. You will add your API endpoints between Build() and Run() in the next step.
- Save the file with Ctrl+S.
- Verify the project builds successfully by running this in the VS Code terminal (Ctrl+` to open it):
dotnet build
You should see Build succeeded with 0 errors in the output.
Seeing build errors?
Make sure you replaced the entire contents of Program.cs and did not leave any leftover code from the template. The file should contain exactly three lines.
help me fix build errors in my .NET 10 TaskApi project after replacing Program.cs
✔️ Awesome, I've got everything!
Great. Double check you have saved Program.cs and that dotnet build reports no errors.
ⓧ I'd like to double check the full code
Here is what each file should look like at this point.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Your development environment is fully set up. Next up, you will define your data model and connect it to a database using Entity Framework Core.
Define Your Data Model and Database
Your development environment is ready and your project is scaffolded. Now it's time to define what data your API will store and how it connects to a database.
Every API needs a data model that describes the shape of its resources. You'll use Entity Framework Core, a .NET ORM, to define your model in C# and automatically create a SQLite database from it.
In this step, get ready to:
- Create the data model and database context classes.
- Install and configure Entity Framework Core with SQLite.
- Run your first database migration.
Create the Task model and database context
Your API manages tasks, so you need a C# class that defines what a task looks like. You also need a database context class that tells Entity Framework Core how to store those tasks.
- In the VS Code Explorer sidebar, right-click inside your TaskApi project folder.
- Click New Folder and name it Models.
- Right-click the Models folder and click New File.
- Name the file TaskItem.cs.
- Add the Task model to TaskItem.cs by pasting this code:
namespace TaskApi.Models;
public class TaskItem
{
// Auto-incremented primary key for the database
public int Id { get; set; }
// The task's display name
public string Title { get; set; } = string.Empty;
// Optional longer description of what the task involves
public string Description { get; set; } = string.Empty;
// Tracks whether the task has been finished
public bool IsComplete { get; set; } = false;
// Optional deadline for the task
public DateTime? DueDate { get; set; }
}
What does this code do?
This class defines the shape of a single task in your system. Each property maps to a column in the database table.
- Id is the primary key. Entity Framework Core auto-increments it for each new task.
- Title and Description default to empty strings so they're never null.
- DueDate uses DateTime? (nullable) because not every task needs a deadline.
- Save Models/TaskItem.cs.
- Confirm the file appears under the Models folder in the Explorer sidebar.
Don't see the Models folder?
Make sure you created the folder inside the TaskApi project root (the same level as Program.cs), not inside a subfolder like bin or obj.
help me find where to create my Models folder
Now you need a database context. This is the bridge between your C# model and the actual database.
- Right-click inside your TaskApi project folder again and click New Folder.
- Name the folder Data.
- Right-click the Data folder and click New File.
- Name the file TaskDb.cs.
- Add the database context to TaskDb.cs by pasting this code:
using Microsoft.EntityFrameworkCore;
using TaskApi.Models;
namespace TaskApi.Data;
public class TaskDb : DbContext
{
// Constructor passes configuration options to the base DbContext
public TaskDb(DbContextOptions<TaskDb> options) : base(options) { }
// Exposes the Tasks table so you can query and save TaskItem records
public DbSet<TaskItem> Tasks => Set<TaskItem>();
}
What is a DbContext?
A DbContext is Entity Framework Core's main class for interacting with a database. It manages database connections, tracks changes to your objects, and translates C# operations into SQL queries.
DbSet<TaskItem> Tasks tells EF Core that you want a table called Tasks in your database, where each row is a TaskItem.
- Save Data/TaskDb.cs.
- Confirm the file appears under the Data folder in the Explorer sidebar.
Seeing red squiggly lines in TaskDb.cs?
If you see errors under DbContext or DbSet, don't worry. You haven't installed the Entity Framework Core packages yet. The errors will disappear after the next substep.
help me understand the red errors in my TaskDb.cs file
Install Entity Framework Core and configure SQLite
Entity Framework Core needs NuGet packages to connect to SQLite. You also need the dotnet-ef command-line tool to create and manage database migrations.
- Install the EF Core packages and CLI tool by running these commands in your terminal:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
What do these packages do?
- Microsoft.EntityFrameworkCore.Sqlite is the database provider that lets EF Core talk to SQLite.
- Microsoft.EntityFrameworkCore.Design enables the migration tooling so you can create and apply database schemas from the command line.
- dotnet-ef is the global CLI tool that runs migration commands like dotnet ef migrations add.
- Verify the tool installed by running:
dotnet ef
- You should see a unicorn ASCII art and a list of available commands.
Seeing 'command not found' for dotnet ef?
Close and reopen your terminal, then try again. Global tools sometimes need a fresh terminal session to appear on your PATH.
help me fix dotnet ef command not found
Now you need to tell your application where the SQLite database lives. This is done through a connection string.
- Open appsettings.json in VS Code. It already has logging configuration from the project scaffold.
- Replace the entire contents with this code to add the connection string:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Tasks": "Data Source=tasks.db"
}
}
What is this connection string?
Data Source=tasks.db tells SQLite to create (or open) a file called tasks.db in your project folder. All your task data lives in this single file. No database server required.
- Save appsettings.json.
Finally, wire the database into your application's startup code.
- Open Program.cs and replace the entire contents with this code:
using Microsoft.EntityFrameworkCore;
using TaskApi.Data;
using TaskApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Read the connection string from appsettings.json, fall back to a default
var connectionString = builder.Configuration.GetConnectionString("Tasks") ?? "Data Source=tasks.db";
// Register TaskDb as a service using the SQLite provider
builder.Services.AddSqlite<TaskDb>(connectionString);
var app = builder.Build();
app.Run();
What does this code do?
- The three using statements import Entity Framework Core, your database context, and your model.
- GetConnectionString("Tasks") reads the Tasks key from the ConnectionStrings section in appsettings.json.
- AddSqlite<TaskDb>(connectionString) registers your TaskDb class as a service so your API endpoints can use it to read and write tasks.
- Save Program.cs.
- Confirm there are no red error squiggles in Program.cs or Data/TaskDb.cs. The packages are now installed so all types should resolve.
Still seeing errors in Program.cs?
Make sure you saved all files. If errors persist, try closing and reopening VS Code. The C# Dev Kit extension sometimes needs a moment to detect new packages.
help me resolve unresolved type errors in Program.cs after installing EF Core packages
✔️ Awesome, I've got everything!
Great. Double check you've saved all your files before moving on.
ⓧ I'd like to double check the full code
Here's what each file should look like at this point:
Models/TaskItem.cs
namespace TaskApi.Models;
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsComplete { get; set; } = false;
public DateTime? DueDate { get; set; }
}
Data/TaskDb.cs
using Microsoft.EntityFrameworkCore;
using TaskApi.Models;
namespace TaskApi.Data;
public class TaskDb : DbContext
{
public TaskDb(DbContextOptions<TaskDb> options) : base(options) { }
public DbSet<TaskItem> Tasks => Set<TaskItem>();
}
Program.cs
using Microsoft.EntityFrameworkCore;
using TaskApi.Data;
using TaskApi.Models;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Tasks") ?? "Data Source=tasks.db";
builder.Services.AddSqlite<TaskDb>(connectionString);
var app = builder.Build();
app.Run();
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Tasks": "Data Source=tasks.db"
}
}
TaskApi.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" />
</ItemGroup>
</Project>
Run your first database migration
A migration is a set of instructions that creates or updates your database schema to match your C# model. EF Core compares your model classes to the current database state and generates the SQL needed to bring them in sync.
- Create your first migration by running this command:
dotnet ef migrations add InitialCreate
- You should see a message indicating the migration was created. A new Migrations folder appears in your project with timestamped migration files.
- Apply the migration to create the actual database file by running:
dotnet ef database update
- Verify the database was created by running:
ls tasks.db
- You should see tasks.db listed in the output. This is your SQLite database file.
What just happened?
dotnet ef migrations add InitialCreate generated C# code describing how to create the Tasks table with columns matching your TaskItem properties.
dotnet ef database update executed that migration against your SQLite file, creating tasks.db with the correct schema. Your database is now ready to store tasks.
Seeing 'Your startup project doesn't reference Microsoft.EntityFrameworkCore.Design'?
Make sure you ran dotnet add package Microsoft.EntityFrameworkCore.Design and that your TaskApi.csproj contains the Design package reference. Save all files and try again.
help me fix EF Core migration errors in my .NET project
Your data layer is complete. You have a model, a database context, and a live SQLite database ready to store tasks. Next up, you'll build the API endpoints that let clients create, read, update, and delete tasks through HTTP requests.
Build the CRUD API Endpoints
Your database is ready and waiting for data. Now it is time to build the API endpoints that let clients create, read, update, and delete tasks.
Each endpoint maps an HTTP method (GET, POST, PUT, DELETE) to a C# handler function. ASP.NET Core Minimal APIs let you define these handlers as compact lambda expressions, similar to how Express.js routes work.
In this step, get ready to:
- Build GET endpoints to retrieve tasks from the database.
- Build POST and PUT endpoints to create and update tasks.
- Build a DELETE endpoint and verify the full CRUD cycle locally.
Create the route group and GET endpoints
A route group lets you define a shared URL prefix for related endpoints. All your task endpoints will live under /tasks.
- In VS Code, open Program.cs.
- Find the line app.Run(); at the bottom of the file.
- Add the following code directly above app.Run(); to create the route group and a GET endpoint that returns all tasks:
var tasks = app.MapGroup("/tasks");
tasks.MapGet("/", async (TaskDb db) =>
await db.Tasks.ToListAsync());
What does this code do?
app.MapGroup("/tasks") creates a route group so every endpoint you add to tasks automatically starts with /tasks.
The MapGet handler uses async/await to fetch all task records from the SQLite database via Entity Framework Core. ASP.NET Core automatically injects the TaskDb instance as a parameter.
You will not see a change yet. The next chunk adds the second GET endpoint for retrieving a single task.
- Directly below the MapGet("/", ...) block you just added, paste this code to retrieve a single task by its ID:
tasks.MapGet("/{id}", async (int id, TaskDb db) =>
await db.Tasks.FindAsync(id)
is TaskItem task
? Results.Ok(task)
: Results.NotFound());
What does this code do?
This endpoint extracts the id from the URL path (e.g. /tasks/3) and looks it up in the database with FindAsync.
The is TaskItem task pattern checks whether the result is a non-null TaskItem. If it is, the endpoint returns 200 OK with the task. Otherwise it returns 404 Not Found.
- Save Program.cs.
- Start the API by running this command in your terminal:
dotnet run
What should I see?
Your terminal displays a message like Now listening on: http://localhost:XXXX where XXXX is the port your project uses. Note this URL. You will use it in the next command.
- Open a second terminal in VS Code by clicking Terminal in the top menu, then New Terminal.
- Test the GET all tasks endpoint by running this command (replace the port with yours):
curl http://localhost:[[PORT="your port number from the terminal output"]]/tasks
- You should see an empty JSON array [] because no tasks exist yet.
Seeing connection refused?
- Make sure dotnet run is still running in your first terminal. The server must stay active while you test.
- Double check the port number matches what your terminal displays after Now listening on:.
- If you see a build error, check Program.cs for missing semicolons or mismatched parentheses.
Why can't I reach my API endpoint?
- Stop the server by pressing Ctrl+C in the first terminal. You will restart it after adding more endpoints.
Create the POST and PUT endpoints
GET endpoints read data. Now you need endpoints that write data. POST creates a new task, and PUT updates an existing one.
- In Program.cs, find the MapGet("/{id}", ...) block you just added.
- Add the following POST endpoint directly below it:
tasks.MapPost("/", async (TaskItem task, TaskDb db) =>
{
db.Tasks.Add(task);
await db.SaveChangesAsync();
return Results.Created($"/tasks/{task.Id}", task);
});
What does this code do?
ASP.NET Core automatically deserializes the incoming JSON request body into a TaskItem object. The handler adds it to the database and calls SaveChangesAsync() to persist it.
Results.Created returns a 201 Created response with a Location header pointing to the new task's URL.
You will not see a change yet. The next chunk adds the PUT endpoint to update tasks.
- Directly below the MapPost block, add the PUT endpoint:
tasks.MapPut("/{id}", async (int id, TaskItem updatedTask, TaskDb db) =>
{
var task = await db.Tasks.FindAsync(id);
if (task is null) return Results.NotFound();
task.Title = updatedTask.Title;
task.Description = updatedTask.Description;
task.IsComplete = updatedTask.IsComplete;
task.DueDate = updatedTask.DueDate;
await db.SaveChangesAsync();
return Results.NoContent();
});
What does this code do?
The PUT handler finds the existing task by id. If it does not exist, it returns 404 Not Found.
If the task exists, it copies each property from the incoming updatedTask onto the tracked entity, then saves. Results.NoContent() returns a 204 response, which is standard for successful updates with no response body.
- Save Program.cs.
- Start the API again by running:
dotnet run
- In your second terminal, create a new task by running this command (replace the port with yours):
curl -X POST http://localhost:[[PORT="your port number"]]/tasks -H "Content-Type: application/json" -d "{\"title\":\"Buy groceries\",\"description\":\"Milk, eggs, bread\",\"isComplete\":false}"
- You should see a JSON response with your new task, including an id of 1.
- Update that task's title by running:
curl -X PUT http://localhost:[[PORT="your port number"]]/tasks/1 -H "Content-Type: application/json" -d "{\"title\":\"Buy organic groceries\",\"description\":\"Milk, eggs, bread\",\"isComplete\":false}"
- You should see no response body (a 204 No Content response). Confirm the update worked by fetching the task:
curl http://localhost:[[PORT="your port number"]]/tasks/1
- The response should show the updated title Buy organic groceries.
Getting a 404 or empty response?
- Make sure you include the Content-Type: application/json header in your POST and PUT requests. Without it, ASP.NET Core cannot deserialize the body.
- Check that your JSON is valid. Escaped quotes are required when using curl on the command line.
- Verify the port in your curl command matches the one shown in the dotnet run output.
Why isn't my POST or PUT working?
- Stop the server by pressing Ctrl+C in the first terminal.
Create the DELETE endpoint and verify the full CRUD cycle
The final piece is a DELETE endpoint. Once this is in place, your API supports the complete lifecycle of a task: create, read, update, and delete.
- In Program.cs, find the MapPut block you just added.
- Add the DELETE endpoint directly below it (above app.Run();):
tasks.MapDelete("/{id}", async (int id, TaskDb db) =>
{
if (await db.Tasks.FindAsync(id) is TaskItem task)
{
db.Tasks.Remove(task);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
What does this code do?
The DELETE handler looks up the task by id. If it exists, it removes it from the database and returns 204 No Content. If the task is not found, it returns 404.
The is TaskItem task pattern combines the null check and variable assignment into one expression. This is a common C# pattern for concise conditional logic.
- Save Program.cs.
- Start the API one more time:
dotnet run
- In your second terminal, run through the full CRUD cycle. First, create a task:
curl -X POST http://localhost:[[PORT="your port number"]]/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn C#\",\"description\":\"Build a REST API\",\"isComplete\":false}"
- Retrieve all tasks to confirm it was created:
curl http://localhost:[[PORT="your port number"]]/tasks
- Delete the task (use the id from the POST response):
curl -X DELETE http://localhost:[[PORT="your port number"]]/tasks/1
- Fetch all tasks again to confirm it is gone:
curl http://localhost:[[PORT="your port number"]]/tasks
- You should see an empty array [] confirming the task was deleted.
Task still showing after DELETE?
- Make sure you used -X DELETE in your curl command. Without it, curl defaults to a GET request.
- Check that the id in your DELETE URL matches the id returned in your POST response. If you ran earlier tests, the ID might be higher than 1.
Why isn't my DELETE removing the task?
✔️ Awesome, I've got everything!
Great. Double check you have saved Program.cs and that your server is still running.
ⓧ I'd like to double check the full code
Here is the complete Program.cs at this point:
using Microsoft.EntityFrameworkCore;
using TaskApi.Data;
using TaskApi.Models;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Tasks") ?? "Data Source=tasks.db";
builder.Services.AddSqlite<TaskDb>(connectionString);
var app = builder.Build();
var tasks = app.MapGroup("/tasks");
tasks.MapGet("/", async (TaskDb db) =>
await db.Tasks.ToListAsync());
tasks.MapGet("/{id}", async (int id, TaskDb db) =>
await db.Tasks.FindAsync(id)
is TaskItem task
? Results.Ok(task)
: Results.NotFound());
tasks.MapPost("/", async (TaskItem task, TaskDb db) =>
{
db.Tasks.Add(task);
await db.SaveChangesAsync();
return Results.Created($"/tasks/{task.Id}", task);
});
tasks.MapPut("/{id}", async (int id, TaskItem updatedTask, TaskDb db) =>
{
var task = await db.Tasks.FindAsync(id);
if (task is null) return Results.NotFound();
task.Title = updatedTask.Title;
task.Description = updatedTask.Description;
task.IsComplete = updatedTask.IsComplete;
task.DueDate = updatedTask.DueDate;
await db.SaveChangesAsync();
return Results.NoContent();
});
tasks.MapDelete("/{id}", async (int id, TaskDb db) =>
{
if (await db.Tasks.FindAsync(id) is TaskItem task)
{
db.Tasks.Remove(task);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
Your API is fully functional locally. Every REST operation works end-to-end with real database persistence. Next up, you will deploy this API to Azure App Service so anyone on the internet can reach it.
Deploy to Azure App Service
Your API is fully functional on your local machine. Now it is time to make it accessible to the world.
In this step, you will publish your project for production, then deploy it to Azure App Service using the Azure CLI. Once deployed, anyone with the URL can interact with your Task Manager API.
In this step, get ready to:
- Publish your API for production using dotnet publish.
- Deploy to Azure App Service on the Free F1 tier.
- Test your live API and verify data persists across requests.
Publish the API for production
Before deploying, you need to create a production-ready version of your application. The dotnet publish command compiles your code, bundles dependencies, and outputs everything into a single folder ready for hosting.
- Make sure you are in the TaskApi project folder in your terminal (the folder containing TaskApi.csproj).
- Publish your API for production by running this command:
dotnet publish --configuration Release
What does this command do?
The --configuration Release flag tells .NET to optimize the build for production. The output lands in bin/Release/net10.0/publish/ by default.
This folder contains your compiled application, all its dependencies, and configuration files. It is everything Azure needs to run your API.
- Confirm the publish succeeded by checking the output folder exists. Run:
ls bin/Release/net10.0/publish/
You should see files like TaskApi.dll, appsettings.json, and several dependency DLLs listed in the output.
Not seeing the publish folder?
Make sure you ran the command from your TaskApi project root (the folder with TaskApi.csproj). If you are in a different directory, the output path will not match.
help me troubleshoot my dotnet publish command
Deploy to Azure using az webapp up
The az webapp up command is a single-step deployment tool. It creates a Resource Group, an App Service Plan on the Free tier, a Web App, and deploys your code all in one go.
- Navigate into the publish output folder by running:
cd bin/Release/net10.0/publish/
- Deploy your API to Azure by running this command:
az webapp up --sku F1 --name [[APPNAME="your-unique-app-name"]] --os-type linux
What does each flag do?
- --sku F1 selects the Free pricing tier. No charges will apply.
- --name sets a globally unique name for your app. This becomes part of your public URL.
- --os-type linux tells Azure to host on Linux. .NET runs cross-platform, and Linux hosting is lightweight.
Your app name must be unique across all of Azure. Use something like taskapi-yourname-2026. Valid characters are a-z, 0-9, and -.
The command takes a few minutes to complete. You will see messages about creating the resource group, the App Service plan, and the web app. When it finishes, it displays a URL like https://your-app-name.azurewebsites.net.
What is Azure App Service?
Azure App Service is a managed hosting platform for web applications. You do not manage servers or operating system patches. Azure handles all of that for you.
The F1 Free tier gives you 60 CPU minutes per day, 1 GB of RAM, and 1 GB of storage. That is more than enough for a learning project.
Seeing an error about the app name?
If you see an error that the name is already taken, choose a more unique name. Try adding random numbers or your initials to the end.
If the command fails with an authentication error, run az login again and retry.
help me fix my az webapp up deployment error
Test your live API
Your API is now live on the internet. Time to verify it works by creating a task and retrieving it.
- Create a task on your live API by running this command (replace the URL with your app's URL from the previous step):
curl -X POST [[AZUREURL="https://your-app-name.azurewebsites.net"]]/tasks -H "Content-Type: application/json" -d "{\"title\":\"My first cloud task\",\"description\":\"Deployed to Azure\",\"isComplete\":false}"
You should see a JSON response containing your new task with an id field assigned by the database.
- Verify tasks persist by retrieving all tasks. Run:
curl [[AZUREURL="https://your-app-name.azurewebsites.net"]]/tasks
You should see a JSON array containing the task you just created. You can also paste this URL directly into your browser to see the response.
Why does persistence matter here?
The SQLite database lives on the App Service filesystem. Tasks you create via POST are saved to the database and returned by GET on subsequent requests. This proves your full stack is working end-to-end in the cloud.
Getting a 404 or empty response?
Make sure you are hitting /tasks at the end of your URL. The API has no root endpoint, so the base URL alone returns a 404.
If you see a generic Azure error page, wait a minute and try again. The app may still be starting up after deployment.
help me debug my deployed Azure API
Secret mission
Add Filtering and Search
Your API returns every task at once. In a real app, users need to find specific tasks quickly. Can you add query parameters that filter tasks by completion status and search by title?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project uses the Azure App Service F1 Free tier, so your API costs nothing to keep running. There are no ongoing charges unless you explicitly upgrade to a paid plan.
Resources you used:
- Azure Resource Group (contains your App Service Plan and Web App).
- Azure App Service Plan on F1 Free tier.
- Azure Web App (your live API endpoint).
- Local TaskApi project folder (contains SQLite database file).
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your API live for demos and interviews.
- The F1 Free tier has no cost. Your API stays live at its Azure URL indefinitely within the free tier quotas (60 CPU minutes per day).
- Your local project folder and SQLite database remain on your machine for future development.
- The .NET SDK, Azure CLI, and C# Dev Kit extension remain installed for future projects.
Pause - I'll come back to this later
The F1 Free tier has no running cost, so there is nothing to pause on the Azure side. Your API stays live at no charge.
- If you have the local dev server running, press Ctrl+C in the terminal to stop it.
- Your Azure resources remain active on the free tier with no action needed.
- When you come back, open the TaskApi folder in VS Code and run dotnet run to resume local development.
Delete - I don't want to use this again
Remove all Azure resources and delete the local project to start fresh.
First, find your resource group name by running this command:
az group list --output table
Look for the resource group that was created during your deployment. It contains your app name.
- Delete all Azure resources (Resource Group, App Service Plan, and Web App) by running:
az group delete --name [[RESOURCE_GROUP="your-resource-group-name"]] --yes
What does this command do?
The az group delete command removes the entire resource group and everything inside it. This includes your App Service Plan, Web App, and any configuration associated with them.
The --yes flag skips the confirmation prompt so the deletion proceeds immediately.
- Delete the local project folder to remove all source code and the SQLite database:
cd ~\Desktop
Remove-Item -Recurse -Force TaskApi
- Uninstall the dotnet-ef global tool by running:
dotnet tool uninstall --global dotnet-ef
After these steps, all project resources are removed from both Azure and your local machine.
Nice Work!
Nice Work!
You just built a production-style REST API from scratch in C# and deployed it live to the internet. That is a serious backend engineering accomplishment.
Here's what you learned:
- Built a complete CRUD API in C# using ASP.NET Core Minimal APIs, including route groups, lambda expressions, async/await, and typed model classes.
- Persisted data using Entity Framework Core with a SQLite database, creating migrations to manage your schema over time.
- Deployed a live, publicly-accessible API to Azure App Service using the Azure CLI, making your work accessible to anyone on the internet.
- Secret Mission: Added filtering and search to your endpoints using LINQ queries and query string parameters, letting users narrow down results by completion status and title.
Ready to quiz yourself?