mailwal

Master Serverless APIs: Azure Functions HTTP Triggers Deep Dive

DotNetMirror’s Newsletter

Master Serverless APIs: Azure Functions HTTP Triggers Deep Dive

Learn to build scalable serverless REST APIs using Azure Functions, the .NET Isolated Worker model, and advanced HTTP trigger routing.

Welcome back to DotNetMirror!

Have you ever spun up a massive ASP.NET Core Web API project just to handle a couple of simple webhooks or background tasks? It often feels like renting an entire mansion when all you really need is a single hotel room.

Welcome to this week’s deep dive, where we are shifting focus from heavy middleware pipelines to lightweight, highly scalable serverless architecture. Today, we are exploring Azure Functions HTTP Triggers using the modern .NET Isolated Worker Model.

Why Azure Functions for REST APIs?

Azure Functions allow you to run C# code in response to web requests without the overhead of configuring a full web server. The biggest advantage is the Consumption Plan—you pay strictly per execution. If your API gets zero traffic at 3 AM, you pay absolutely nothing, and it auto-scales instantly under heavy load.

By using the Isolated Worker Model, your code runs in a separate process from the Azure Functions host runtime, preventing dependency conflicts and future-proofing your applications (in-process support ends in November 2026). Best of all, the latest updates bring deep ASP.NET Core integration, allowing you to use familiar types like HttpRequest and IActionResult.

Building a Complete CRUD API

Instead of a massive switch statement to check incoming HTTP verbs, the best practice is to adhere to the Single Responsibility Principle: one function per HTTP verb. We use route constraints (like {id:int}) for free URL validation, safely extract optional query strings, and deserialize JSON payloads directly from the request body.

Below is the complete implementation of the Certifications API built on Azure Functions.

Code

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace CertHTTPFunctionApp
{
    public class CertificationFunctions
    {
        private readonly ILogger<CertificationFunctions> _logger;

        public CertificationFunctions(ILogger<CertificationFunctions> logger)
        {
            _logger = logger;
        }

        // Static list to simulate a database
        private static readonly List<Certification> _certifications = new()
        {
            new Certification { Id = 1, Name = "Azure Fundamentals", Category = "Fundamentals", Fee = 165, AvailableSlots = 150 },
            new Certification { Id = 2, Name = "AWS Certified Cloud Practitioner", Category = "Fundamentals", Fee = 100, AvailableSlots = 45 },
            new Certification { Id = 3, Name = "Azure Solutions Architect Expert", Category = "Architecture", Fee = 150, AvailableSlots = 80 },
            new Certification { Id = 4, Name = "AWS Certified SysOps Administrator – Associate", Category = "Administrator", Fee = 150, AvailableSlots = 200 }
        };

        // 1. DEMO: Query Strings
        // URL Example: GET /api/cert?category=Fundamentals&minFee=100
        [Function("GetCertificates")]
        public IActionResult GetCertificates(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "cert")] HttpRequest req)
        {
            _logger.LogInformation("Processing GetCertificates request.");
            
            string? category = req.Query["category"];
            string? minFeeStr = req.Query["minFee"];
            decimal? minFee = decimal.TryParse(minFeeStr, out decimal parsedFee) ? parsedFee : null;

            var query = _certifications.AsQueryable();

            if (!string.IsNullOrEmpty(category))
            {
                query = query.Where(p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase));
            }

            if (minFee.HasValue)
            {
                query = query.Where(p => p.Fee >= minFee.Value);
            }

            return new OkObjectResult(query.ToList());
        }

        // 2. DEMO: Route Parameters
        // URL Example: GET /api/cert/3
        [Function("GetCertificateById")]
        public IActionResult GetCertificateById(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "cert/{id:int}")] HttpRequest req,
            int id)
        {
            var cert = _certifications.FirstOrDefault(p => p.Id == id);
            if (cert == null)
            {
                return new NotFoundObjectResult(new { Message = $"Certificate with ID {id} not found." });
            }

            return new OkObjectResult(cert);
        }

        // 3. DEMO: Request Body
        // URL Example: POST /api/cert
        [Function("CreateCertificate")]
        public IActionResult CreateCertificate(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "cert")] HttpRequest req,
            [FromBody] Certification newCertificate)
        {
            if (newCertificate == null)
            {
                return new BadRequestObjectResult("Certificate data cannot be null.");
            }

            newCertificate.Id = _certifications.Max(p => p.Id) + 1;
            _certifications.Add(newCertificate);

            return new CreatedResult($"/api/cert/{newCertificate.Id}", newCertificate);
        }

        // 4. DEMO: MIXED SOURCES (Route + Body)
        // URL Example: PATCH /api/cert/2/slot
        [Function("UpdateStock")]
        public IActionResult UpdateStock(
            [HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "cert/{id:int}/slot")] HttpRequest req,
            int id,
            [FromBody] UpdateSlotQuantity slotDto)
        {
            var cert = _certifications.FirstOrDefault(p => p.Id == id);
            if (cert == null)
            {
                return new NotFoundObjectResult(new { Message = $"Certificate with ID {id} not found." });
            }

            cert.AvailableSlots += slotDto.NewQuantity;

            return new OkObjectResult(new { Message = $"Slot for cert {id} updated successfully.", Certificate = cert });
        }

        // 5. DEMO: Route Parameter (DELETE)
        // URL Example: DELETE /api/cert/3
        [Function("DeleteCertificate")]
        public IActionResult DeleteCertificate(
            [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "cert/{id:int}")] HttpRequest req,
            int id)
        {
            var cert = _certifications.FirstOrDefault(p => p.Id == id);
            
            if (cert == null)
            {
                return new NotFoundObjectResult(new { Message = $"Certificate with ID {id} not found." });
            }

            _certifications.Remove(cert);

            return new OkObjectResult(new { Message = $"Certificate with ID {id} deleted successfully." });
        }
    }

    public class Certification
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Fee { get; set; }
        public int AvailableSlots { get; set; }
    }

    public class UpdateSlotQuantity
    {
        public int NewQuantity { get; set; }
    }
}

Video Masterclass & Full Source Code

Want to see this built, tested locally using Bruno, and deployed live to the Azure Cloud? I have put together a full 20-minute video masterclass walking through every step.

📺 Watch the full tutorial on YouTube:

💻 Get the complete source code on GitHub: https://github.com/dotnetmirror/CertHTTPFunctionApp

Subscribe to our YouTube channel - https://www.youtube.com/dotnetmirror

© 2026 DotNetMirror
[email protected], Hyderabad, India