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.
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.
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.
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.
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.
Print to the 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:
send-receipt/
index.ts ← the entry file
template.ts
package.jsonThe entry file sits at the top of the folder and is index.ts, index.js or
index.mjs. A different name works with --entry:
naijacloud functions deploy send-receipt --project shop --entry main.tsWhere the CLI looks for your code
naijacloud functions deploy <name> uses the first of these that exists, from
the folder you run it in:
./<name>.ts(a single file)./supabase/functions/<name>/(Supabase's layout)./functions/<name>/./<name>/
Point it anywhere else with --file:
naijacloud functions deploy hello --project shop --file ./src/hello.tsnode_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.
{
"dependencies": {
"zod": "^3.23.8"
}
}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:
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.