Serverless & runtimes
Netlify Functions
Expose a contract as a Web-native Netlify Function with platform context and deployment configuration.
@hulla/api-netlify-functions mounts an @hulla/api server implementation as a modern Web-native Netlify Function.
The adapter accepts Netlify’s Request and Context directly and returns a Web Response.
Prerequisites
This guide starts at the adapter boundary. First define the shared contract from contract authoring, then turn it into the server value mounted below:
// src/api/server.ts
import { defineServer } from '@hulla/api/server'
import { contract } from './contract'
const server = defineServer(contract)
export const implementation = server.implement({
health: ({ response }) => response(200, 'ok'),
users: {
byId: ({ params, response }) =>
response(200, { id: params.id, name: 'Ada' }),
rename: ({ params, body, response }) =>
response(200, { id: params.id, name: body.name }),
},
})defineServer(contract) creates the server authoring scope. implement() requires the handler tree to cover the
contract and returns the complete implementation accepted by mount(). See
server authoring for context, middleware, declared errors, and independently deployable
fragments.
Create the function
// netlify/functions/api.ts
import type { Config } from '@netlify/functions'
import { netlifyFunctionsAdapter } from '@hulla/api-netlify-functions'
import { implementation } from '../../src/api/server'
const adapter = netlifyFunctionsAdapter()
export default adapter.mount(implementation)
export const config: Config = {
path: '/api/*',
}Without a custom path, Netlify exposes the file at /.netlify/functions/api. The example assigns /api/* to the
function so a contract with the /api base path receives URLs such as /api/health unchanged. Keep host routing and
the contract base path aligned; the adapter does not strip or invent prefixes.
The handler delegates to the shared Fetch executor without translating the native request or response. JSON, text, bytes, form data, raw Web responses, and streamed Web responses therefore retain the Fetch adapter’s behavior.
Client consumption
The Netlify adapter only hosts the server implementation. Browser, mobile, and service consumers use the ordinary contract-shaped client against the site or function origin:
import { createClient } from '@hulla/api/client'
import { fetchTransport } from '@hulla/api/fetch'
import { contract } from './api/contract'
export const api = createClient(contract, {
transport: fetchTransport({ baseUrl: 'https://example.netlify.app' }),
})
export async function getUser(id: string) {
const result = await api.users.byId({ params: { id } })
if (result.status === 200) return result.body
if (result.status === 404) return undefined
throw new Error(`Could not load user: ${result.status}`)
}baseUrl is placed before the contract’s basePath. The returned value is a status-discriminated union, so each
declared response narrows its body and headers. See client authoring and
client integrations.
Native context
Use the adapter object’s context() method for Netlify request metadata, geo information, cookies, route parameters,
or deferred work:
import { defineServer } from '@hulla/api/server'
import { netlifyFunctionsAdapter } from '@hulla/api-netlify-functions'
const adapter = netlifyFunctionsAdapter()
const server = defineServer(contract, {
context: adapter.context(({ request, netlifyContext, route }) => {
netlifyContext.waitUntil(writeAuditLog(netlifyContext.requestId))
return {
request,
requestId: netlifyContext.requestId,
country: netlifyContext.geo.country?.code,
route,
}
}),
})request is the same Web Request passed to the function. netlifyContext is Netlify’s native Context, including
params, cookies, geo, ip, site and deploy metadata, requestId, and waitUntil(). An adapter-native context
factory binds every implementation and fragment from that server definition to this adapter.
Pass onError to the adapter or one mount() call to observe failures or return a replacement Web response:
const handler = adapter.mount(implementation, {
onError({ error, phase, netlifyContext, defaultResponse }) {
console.error(phase, netlifyContext.requestId, error)
return defaultResponse
},
})Scope
The adapter targets Netlify’s Web-native synchronous function handler. Scheduled functions, background functions, event-triggered functions, and platform-specific event handlers have distinct invocation and response lifecycles and do not use this adapter. Function paths, supported methods, deployment configuration, and authorization remain under application and Netlify configuration control.