Local prerequisites for an efficient inner loop
A productive Azure Functions workflow relies on a tight “inner loop”: edit code, run locally, trigger the function, debug, and repeat. To do that reliably, you need a few tools installed locally so your machine can emulate the Azure Functions runtime and connect to local emulators or real Azure resources.
Install Azure Functions Core Tools
Azure Functions Core Tools provides the local runtime host (the func CLI) used to create projects, run them locally, and publish later. Install the latest v4 Core Tools to match current Azure Functions runtime behavior.
- Verify installation:
func --version - Useful commands you will use often:
func init,func new,func start
Install the language SDK/runtime
Install the SDK for the language you will use (for example, .NET SDK, Node.js, Python, or Java). The local Functions host will invoke your function code through that language worker/runtime.
- .NET: install the .NET SDK version aligned with your project’s target framework.
- Node.js: install an LTS version; ensure
node --versionworks. - Python: install a supported Python version and ensure
python --versionworks; use virtual environments. - Java: install a supported JDK and build tool (Maven/Gradle) if applicable.
Install VS Code and extensions
VS Code is a common choice because it integrates project scaffolding, local run/debug, and Azure deployment.
- Azure Functions extension (for creating/running/debugging Functions).
- Azure Account extension (for sign-in and resource browsing).
- Language-specific extensions: C# Dev Kit, Python, Java, or Node tooling.
Create a new Azure Functions project
You can scaffold a project either from the command line (repeatable and CI-friendly) or from VS Code (guided UI). The result is the same: a folder containing host configuration plus one or more function entry points.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
Option A: Create from the CLI
1) Create a folder and initialize a Functions project:
mkdir MyFunctionsApp && cd MyFunctionsApp func init2) Choose a worker runtime when prompted (for example: dotnet, node, python).
3) Add a function (choose a trigger template):
func new4) Select a trigger (for example HTTP trigger, Timer trigger, Queue trigger) and provide a function name.
Option B: Create from VS Code
1) Open the Command Palette and run “Azure Functions: Create New Project”.
2) Pick a folder, language, and a trigger template.
3) VS Code will scaffold the project and can automatically create a debug configuration.
Understand the key project files
Azure Functions projects have a small set of important files that control runtime behavior, local configuration, and function entry points. Knowing what they do helps you debug issues quickly and structure code cleanly.
host.json: host-level runtime configuration
host.json configures behavior for the Functions host and certain bindings. It applies across all functions in the app. Typical uses include logging configuration, extension/binding settings, and concurrency controls (depending on runtime and language).
Example (illustrative):
{ "version": "2.0", "logging": { "logLevel": { "default": "Information", "Host.Results": "Information", "Function": "Information" } } }Keep host.json focused on runtime concerns. Avoid putting environment-specific secrets here.
local.settings.json: local-only app settings and connection strings
local.settings.json stores settings used when running locally, including Values (environment variables) and connection strings for triggers/bindings. This file is intended for local development only and should not be committed to source control when it contains secrets.
Common patterns:
Valuescontains settings likeAzureWebJobsStorage, feature flags, and your own configuration keys.- Use separate settings per developer via local overrides, or provide a sanitized template file (for example
local.settings.template.json) in the repo.
Example:
{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "dotnet", "AzureWebJobsStorage": "UseDevelopmentStorage=true", "MyApi__BaseUrl": "https://localhost:5005", "MyFeatureFlag": "true" } }Note the double-underscore convention (MyApi__BaseUrl) which maps cleanly to hierarchical configuration in many frameworks.
Function entry points: where triggers connect to your code
Each function has an entry point that the runtime calls when the trigger fires. The exact shape depends on language and model, but the principle is the same: the entry point should be thin and delegate to testable business logic.
Typical responsibilities of an entry point:
- Accept trigger input (HTTP request, queue message, timer tick, etc.).
- Validate and parse input.
- Call application/business services.
- Return output (HTTP response, queue output, etc.).
Keep trigger-specific code (bindings, request/response types, serialization) at the edges.
Run functions locally
Running locally starts the Functions host, loads your functions, and listens for triggers. This is the foundation of your inner loop.
Start the local host
From the project folder:
func startThe host will print discovered functions and their endpoints (for HTTP triggers). If something fails to load, the console output usually points to missing settings, missing storage configuration, or language runtime issues.
Common local settings you must have
FUNCTIONS_WORKER_RUNTIME: identifies the language worker.AzureWebJobsStorage: required by many triggers/bindings; for local development you can use an emulator connection string if available, or a real Azure Storage account connection string.
Debugging locally in VS Code
Debugging lets you set breakpoints inside your function code and inspect variables when triggers fire.
Set up a debug configuration
VS Code typically generates a launch.json configuration when you create the project. If not, use the Azure Functions extension to add it. The debug configuration starts the Functions host and attaches the debugger to the language runtime.
Debug workflow
- Set breakpoints in the function entry point and in the business logic it calls.
- Start debugging (Run and Debug panel).
- Trigger the function (HTTP call, queue message, timer simulation).
- Step through code, inspect locals, and evaluate expressions.
If breakpoints are not hit, verify you are running the debug configuration (not just func start in a terminal), and confirm symbols/source maps are generated for your language.
Simulate triggers during local development
To keep the inner loop fast, you should be able to fire triggers on demand without deploying.
HTTP triggers: call endpoints directly
When the host starts, it prints the local URL for each HTTP function. You can invoke it with a browser, curl, or an API client.
Example with curl:
curl -i http://localhost:7071/api/Hello?name=AzureExample POST with JSON:
curl -i -X POST http://localhost:7071/api/ProcessOrder -H "Content-Type: application/json" -d '{"orderId":"123","amount":42.5}'Timer triggers: test without waiting
Timer triggers run on a schedule. For local testing, you can temporarily adjust the schedule to run more frequently (for example every 10 seconds) and then revert it. Keep these changes local or controlled via configuration so you do not accidentally deploy an aggressive schedule.
Queue/Service Bus/Event triggers: use emulators or real resources
For triggers that depend on external infrastructure, you have two practical approaches:
- Use a local emulator where available (commonly for Azure Storage). Configure
AzureWebJobsStorageaccordingly. - Use a dedicated development resource in Azure (recommended for Service Bus, Event Hubs, Event Grid). Store connection strings in
local.settings.jsonand rotate them as needed.
To simulate a queue trigger, enqueue a test message using a script, CLI, or a small helper tool. The function should pick it up immediately when the host is running.
Structure code for testability
Functions are easiest to maintain when the trigger handler is a thin adapter and the business logic lives in plain, testable components. This lets you unit test without running the Functions host and without needing trigger-specific types.
Separate trigger handlers from business services
Recommended layering:
- Function entry point (adapter): deals with trigger input/output, logging scope, and translating to domain types.
- Application/service layer: orchestrates use cases (for example, “process order”, “send notification”).
- Domain logic: pure logic and validation, ideally with minimal dependencies.
- Infrastructure clients: HTTP clients, storage repositories, messaging publishers.
In practice, the function entry point should do little more than: parse input, call a service, map result to output.
Example pattern (pseudocode)
// Function entry point (trigger adapter) public async Task<HttpResponse> Run(HttpRequest req) { var dto = Parse(req); var result = await _orderService.ProcessAsync(dto); return ToHttpResponse(result); } // Business logic (testable) public class OrderService { public Task<ProcessResult> ProcessAsync(OrderDto dto) { // validate, compute, call repositories/clients } }With this split, you can unit test OrderService with standard test frameworks, mocking repositories/clients, without any Functions runtime involvement.
Dependency injection for clean composition
Use dependency injection (where supported by your chosen language/model) to provide services and clients to your function entry points. This enables:
- Mocking dependencies in unit tests.
- Centralized configuration of HTTP clients, serializers, and retry policies.
- Clear separation between runtime wiring and business logic.
Environment-based configuration for local runs
Local development needs different settings than cloud environments. The goal is to make configuration predictable and safe: no secrets in code, no accidental production connections, and easy switching between dev/test environments.
Use app settings (environment variables) as the source of truth
When running locally, local.settings.json populates environment variables for the Functions host. Your code should read configuration from the environment/configuration system rather than hard-coding values.
- Store connection strings and API keys in
local.settings.json(local only). - In Azure, store the same keys as Function App application settings.
- Keep key names consistent across environments so code does not change.
Use configuration binding and validation
Prefer strongly-typed configuration objects (where available) and validate them at startup or first use. This catches missing settings early in the inner loop.
Example keys:
MyApi__BaseUrlMyApi__ApiKeyFeatureFlags__UseNewFlow
Provide a safe template for developers
To keep onboarding smooth without leaking secrets:
- Commit a
local.settings.template.jsonwith placeholder values. - Add
local.settings.jsonto.gitignore. - Document required keys in a short README section or in code comments near configuration binding.
Switching environments during local runs
If you need to run locally against different backends (for example, dev vs. test), avoid editing code. Instead:
- Maintain multiple local settings files (for example
local.settings.dev.json,local.settings.test.json) and copy/symlink the one you need tolocal.settings.jsonbefore running. - Or use shell environment variables to override specific keys for a session.
This keeps the inner loop fast while reducing the risk of committing secrets or accidentally pointing local runs at production resources.