benjamin-abt.com

Azure Static Web Apps is primarily a host for static frontend assets, but it can also deploy a small HTTP API alongside the frontend. That API is implemented as an Azure Functions application and is exposed through the same site under the fixed /api path. Hono fits this model well when a web application needs a focused JSON API without introducing a separately operated container or web host.

The architecture is deliberately narrow. Static HTML, JavaScript, CSS and assets are served by Azure Static Web Apps. Hono owns the request routing, middleware and HTTP responses inside an Azure Functions v4 handler. The frontend calls /api/... on its own origin, so the platform routes the request to the API without a second public hostname or a browser CORS configuration.

This is not a general replacement for Azure Container Apps or a dedicated Function App. Managed Functions in Static Web Apps are intentionally constrained: they support HTTP triggers only, do not provide Managed Identity or Key Vault references, and are not externally reachable apart from the Static Web App. Those limits are useful because they keep the deployment simple, but they define the boundary of the model.

The right workload for Static Web Apps and Hono

The combination is strongest for a static or single-page frontend with a small backend-for-frontend API. Typical examples include a product dashboard, a documentation portal with a feedback endpoint, an authenticated internal tool or a marketing site with a form submission API.

The main benefit is a single deployment unit. A pull request can produce one preview URL containing both frontend and API behavior. Production uses one public origin, which reduces routing, certificate and CORS concerns. The Hono application remains independently testable through app.request(...), while Azure Functions remains only the host adapter.

The model becomes less appropriate when the backend needs queue, timer, Event Grid or Durable Functions triggers; private network access; Managed Identity; Key Vault references; long-running processing; or an API that must be consumed outside the Static Web App. In those cases, Azure Static Web Apps can connect to an existing Function App instead. That “bring your own Functions” configuration preserves the /api integration but moves deployment and operational responsibility to the separate Function App.

Project structure

A small project can keep the frontend and the API as sibling directories. The build action receives the two locations separately.

 1.
 2|- app/
 3|  |- public/
 4|  |  `- staticwebapp.config.json
 5|  `- src/
 6`- api/
 7   |- host.json
 8   |- package.json
 9   |- tsconfig.json
10   `- src/
11      |- app.ts
12      `- functions/
13         `- hono-api.ts

staticwebapp.config.json belongs in the deployed frontend output. With a frontend build, placing the file in the framework’s public directory ensures it is copied to the root of the final output directory.

Build the Hono application as an Azure Functions v4 API

Hono does not need to know that it runs in Azure. Routes and middleware remain in an ordinary application module, which makes them portable to other Hono adapters and simple to test.

 1// api/src/app.ts
 2import { Hono, type Context } from 'hono'
 3import { secureHeaders } from 'hono/secure-headers'
 4
 5type HealthResponse = {
 6  status: 'ok'
 7}
 8
 9type GreetingResponse = {
10  message: string
11}
12
13const healthResponse: HealthResponse = {
14  status: 'ok',
15}
16
17const app: Hono = new Hono()
18
19app.use('*', secureHeaders())
20
21app.get('/api/health', (context: Context) => {
22  return context.json(healthResponse)
23})
24
25app.get('/api/greeting/:name', (context: Context) => {
26  const name: string = context.req.param('name')
27  const response: GreetingResponse = {
28    message: `Hello ${name}`,
29  }
30
31  return context.json(response)
32})
33
34export default app

The Functions v4 programming model registers an HTTP handler in code. @marplex/hono-azurefunc-adapter connects the Azure Functions request to Hono’s Fetch-style handler. It is a community adapter rather than a Hono or Azure Functions runtime package, so its release cadence, supported Node.js versions and behavior should be evaluated as part of a production dependency review.

 1// api/src/functions/hono-api.ts
 2import { app as functionsApp } from '@azure/functions'
 3import { azureHonoHandler } from '@marplex/hono-azurefunc-adapter'
 4import honoApp from '../app.js'
 5
 6functionsApp.http('hono-api', {
 7  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
 8  authLevel: 'anonymous',
 9  route: '{*proxy}',
10  handler: azureHonoHandler(honoApp.fetch),
11})

authLevel: 'anonymous' is correct for this host integration because Static Web Apps performs route authorization before forwarding to the Function. It does not mean that every Hono endpoint should be public. Application-specific authorization must still be enforced in the Static Web Apps configuration and, for sensitive data, in the API itself.

Azure Functions normally prefixes HTTP routes with /api. The Static Web Apps public API path is already /api, so the Function host should not add another prefix. The following host.json lets the catch-all Hono route receive the complete public path.

1{
2  "version": "2.0",
3  "extensions": {
4    "http": {
5      "routePrefix": ""
6    }
7  }
8}

The API project needs the Function runtime SDK, Hono and the adapter:

1npm install hono @azure/functions @marplex/hono-azurefunc-adapter
2npm install --save-dev typescript @azure/functions-core-tools

The following package configuration makes Azure Functions discover compiled v4 handlers. The TypeScript compiler settings are intentionally conventional for a Node.js ESM project.

 1{
 2  "name": "swa-hono-api",
 3  "private": true,
 4  "type": "module",
 5  "main": "dist/src/functions/*.js",
 6  "scripts": {
 7    "build": "tsc -p tsconfig.json",
 8    "start": "func start"
 9  }
10}
 1{
 2  "compilerOptions": {
 3    "target": "ES2022",
 4    "module": "NodeNext",
 5    "moduleResolution": "NodeNext",
 6    "outDir": "dist",
 7    "rootDir": ".",
 8    "strict": true,
 9    "skipLibCheck": true
10  },
11  "include": ["src/**/*.ts"]
12}

With the local Functions host running, GET http://localhost:7071/api/health exercises the same Hono routing path that Static Web Apps will forward in Azure. A route test can avoid the host entirely:

1const response: Response = await app.request('http://localhost/api/health')
2const payload: HealthResponse = await response.json()
3
4if (payload.status !== 'ok') {
5  throw new Error('Health endpoint returned an unexpected response.')
6}

Configure the Static Web App

The configuration file selects the Node.js version for managed Functions and can protect API routes with Static Web Apps roles. Rules are evaluated in order, so more specific method rules must appear first.

 1{
 2  "platform": {
 3    "apiRuntime": "node:22"
 4  },
 5  "routes": [
 6    {
 7      "route": "/api/admin/*",
 8      "methods": ["POST", "PUT", "PATCH", "DELETE"],
 9      "allowedRoles": ["administrator"]
10    },
11    {
12      "route": "/api/*",
13      "allowedRoles": ["authenticated"]
14    }
15  ]
16}

The platform authorization is valuable because rejected requests do not reach the Function. However, client-side routes are not a security boundary, and route rules alone should not be the only control for data with tenant, ownership or record-level rules. Hono middleware or the application service layer must validate those domain rules using trustworthy identity information.

No CORS middleware is required when the browser frontend calls its same-origin /api endpoints. Adding a permissive Access-Control-Allow-Origin: * header in that case only widens the public surface. If a different browser origin must call the API, the design has changed and CORS policy should be specified deliberately in the API response.

Deploy frontend and API together

The generated Azure Static Web Apps GitHub Action accepts an api_location in addition to the frontend application and output locations. This fragment assumes that app builds to app/dist and the Function project is in api.

1- name: Build and deploy
2  uses: Azure/static-web-apps-deploy@v1
3  with:
4    azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
5    repo_token: ${{ secrets.GITHUB_TOKEN }}
6    action: upload
7    app_location: "app"
8    api_location: "api"
9    output_location: "dist"

The action builds the frontend and packages the managed Functions API. A pull request preview created by this workflow includes both parts, which allows a UI change and the API contract it depends on to be tested at the same preview URL. The deployment token must remain a GitHub repository secret rather than a value committed to the workflow.

Advantages in practice

The first advantage is deployment simplicity. One platform handles global static asset delivery, custom domains, TLS, preview environments and a nearby HTTP API. A separate reverse proxy, API hostname and browser CORS setup are not required for the common same-origin case.

The second advantage is an appropriate level of serverless abstraction. Hono remains a small and testable TypeScript application, while Static Web Apps handles frontend delivery and Azure Functions handles HTTP invocation. There is no container image, process manager or Kubernetes manifest for a modest backend-for-frontend API.

The third advantage is a clear growth path. A Hono application that keeps Azure-specific access at its boundary can move to a dedicated Azure Function App, Container Apps or another host later. The routing and HTTP contracts do not need to be rewritten merely because the platform boundary changes.

There are also material trade-offs. Managed Functions are not a private-network API tier and cannot use Managed Identity. They are HTTP-only, so a queue consumer or scheduled workflow needs a separate Function App or a different Azure service. Cold-start behavior, Functions quotas and downstream connection capacity still need realistic validation. Static Web Apps reduces infrastructure work; it does not remove API security, observability, data access or performance engineering.

Conclusion

Hono on Azure Static Web Apps is a focused full-stack pattern: static content is served by the Static Web Apps platform, and a managed Azure Functions API handles the small amount of dynamic HTTP behavior behind /api. It is especially effective for frontend-led applications where a single origin, preview environments and minimal infrastructure are more valuable than backend platform flexibility.

The key is to treat the managed Functions limits as part of the architecture. For HTTP-only APIs with modest Azure integration needs, the model stays compact and practical. When managed identity, private networking, non-HTTP triggers or an independently reachable backend become requirements, a separate Function App or Azure Container Apps is the more honest boundary. Hono can keep the application core portable across both choices.


Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms
Modernization
Training & Consulting