Naijacloud
Docs

Write a function

A function is a handler that takes a standard Request and returns a standard Response. Write it in TypeScript or JavaScript, in a single file or a folder with an entry file.

5 min read

Two ways to write a handler

Both are TypeScript or JavaScript, saved as .ts, .js or .mjs.

Node style. Export a handler that takes a Request and returns a Response. You can use process.env, Buffer and node: modules such as node:crypto.

hello.ts
ts
export default async function handler(req: Request) { const name = process.env.GREETING ?? "world"; return Response.json({ message: `Hello ${name}` }); }

Supabase / Deno style. Pass your handler to Deno.serve. This is the shape Supabase Edge Functions use, so a Supabase function deploys without changes.

hello.ts
ts
Deno.serve(async (req: Request) => { const name = Deno.env.get("GREETING") ?? "world"; return Response.json({ message: `Hello ${name}` }); });

export default { fetch(req) { … } } works too.

Read the request

req is a standard Request, the same object fetch uses in a browser.

echo.ts
ts
export default async function handler(req: Request) { const url = new URL(req.url); if (req.method !== "POST") { return new Response("Use POST", { status: 405 }); } const body = await req.json(); // or req.text(), req.formData() const token = req.headers.get("authorization"); const page = url.searchParams.get("page"); // ?page=2 return Response.json({ received: body, page, signedIn: Boolean(token) }); }

Return any Response: Response.json(data) for JSON, new Response(text) for plain text, a status with { status: 404 }, and headers with { headers }. Anything your code throws becomes a 500, and the error is in the function's logs.

console.log, console.warn and console.error all land in the function's Logs, under the call that printed them. See Deploy, logs and delete.

One file or a folder

A single file is the simplest function. When you need more than one file, or npm packages, use a folder:

Code
send-receipt/ index.ts ← the entry file template.ts package.json

The entry file sits at the top of the folder and is index.ts, index.js or index.mjs. A different name works with --entry:

Terminal
bash
naijacloud functions deploy send-receipt --project shop --entry main.ts

Where the CLI looks for your code

naijacloud functions deploy <name> uses the first of these that exists, from the folder you run it in:

  1. ./<name>.ts (a single file)
  2. ./supabase/functions/<name>/ (Supabase's layout)
  3. ./functions/<name>/
  4. ./<name>/

Point it anywhere else with --file:

Terminal
bash
naijacloud functions deploy hello --project shop --file ./src/hello.ts

node_modules, .env files and other dotfiles are never uploaded. Variables belong on the platform: see Secrets and variables.

npm packages

Put a package.json next to your entry file and import packages by name. The dependencies are installed when the deploy builds, so you don't upload node_modules.

package.json
json
{ "dependencies": { "zod": "^3.23.8" } }
index.ts
ts
import { z } from "zod"; const Order = z.object({ email: z.string().email(), amount: z.number().int() }); export default async function handler(req: Request) { const parsed = Order.safeParse(await req.json()); if (!parsed.success) return Response.json(parsed.error.issues, { status: 400 }); return Response.json({ ok: true }); }

Commit a lockfile (package-lock.json) next to it if you want exact versions. An import that can't be found fails the deploy, and the version before it keeps serving, so a bad import never reaches a call.

Paths

Your function's URL ends in /functions/v1/<name>, and anything after the name is passed through. The function sees the path with /functions/v1 taken off, so a call to /functions/v1/hello/receipts arrives as /hello/receipts:

hello.ts
ts
export default async function handler(req: Request) { const { pathname } = new URL(req.url); if (pathname === "/hello/receipts") return Response.json([]); return Response.json({ message: "Hello" }); }

A router that expects that base path, such as Hono's basePath('/hello'), works unchanged.

Names

Function names use lower-case letters, numbers and dashes, up to 40 characters, and cannot start or end with a dash. send-receipt is a good name; Send_Receipt is not, and the CLI suggests the nearest valid one. Each name is unique in a project's environment, and the name is part of the URL.