Examples
Four functions that cover what most frontends need a backend for. Each one is a single file you can deploy as it is, once you've set the variables it reads.
Each example reads its secrets from variables. Set them once per project with
naijacloud env set NAME --service functions-runtime --project <your-project> --secret,
then deploy. If it is the project's first function, deploy it once first: that
creates the functions-runtime service the variables belong to. See
Secrets and variables.
A Paystack webhook
Paystack signs every webhook with your secret key. This function checks the signature before trusting the event, so nobody can fake a payment by calling your URL.
import { createHmac, timingSafeEqual } from "node:crypto";
export default async function handler(req: Request) {
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
// Sign the raw body exactly as it arrived; parsing it first changes it.
const raw = await req.text();
const expected = createHmac("sha512", process.env.PAYSTACK_SECRET_KEY ?? "")
.update(raw)
.digest("hex");
const given = req.headers.get("x-paystack-signature") ?? "";
const valid =
given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!valid) return new Response("Bad signature", { status: 401 });
const event = JSON.parse(raw);
if (event.event === "charge.success") {
const { reference, amount, customer } = event.data;
console.log(`paid: ${reference} ${amount / 100} NGN from ${customer.email}`);
// Mark the order paid in your database here.
}
// Answer 200 quickly, or Paystack retries the event.
return new Response("ok");
}naijacloud env set PAYSTACK_SECRET_KEY --service functions-runtime --project shop --secret
naijacloud functions deploy paystack-webhook --project shopPaste the URL the CLI prints into Paystack → Settings → API Keys & Webhooks → Webhook URL.
Call an AI API with your key
Your frontend calls this function, and the function calls the AI API with a key the browser never sees. This one uses the Anthropic API; any provider with an HTTPS API works the same way.
export default async function handler(req: Request) {
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
const { question } = await req.json();
if (typeof question !== "string" || question.length > 2000) {
return Response.json({ error: "Send a question under 2,000 characters" }, { status: 400 });
}
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY ?? "",
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-5-5",
max_tokens: 500,
messages: [{ role: "user", content: question }],
}),
});
if (!res.ok) {
console.error("AI API error", res.status, await res.text());
return Response.json({ error: "The AI service did not answer" }, { status: 502 });
}
const data = await res.json();
return Response.json({ answer: data.content[0].text });
}naijacloud env set ANTHROPIC_API_KEY --service functions-runtime --project shop --secret
naijacloud functions deploy ask --project shopAnyone with the URL can call it and spend your API credit, so check who is calling before you put it in production: a token from your own sign-in, or a rate limit in your database.
Send an email
This sends a receipt through Naijamail, using a workspace
API key with the Email send scope. The from address must be on a domain
you have verified for email.
export default async function handler(req: Request) {
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
if (req.headers.get("authorization") !== `Bearer ${process.env.INTERNAL_TOKEN}`) {
return new Response("Unauthorized", { status: 401 });
}
const { to, orderId, total } = await req.json();
const res = await fetch("https://api.naijacloud.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NC_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `receipt-${orderId}`,
},
body: JSON.stringify({
from: "Shop <receipts@shop.ng>",
to: [to],
subject: `Your receipt for order ${orderId}`,
text: `Thanks for your order. Total: ₦${total}.`,
}),
});
if (!res.ok) {
console.error("email failed", res.status, await res.text());
return Response.json({ sent: false }, { status: 502 });
}
return Response.json({ sent: true });
}naijacloud env set NC_API_KEY --service functions-runtime --project shop --secret
naijacloud env set INTERNAL_TOKEN --service functions-runtime --project shop --secret
naijacloud functions deploy send-receipt --project shopThe Idempotency-Key means a retried call for the same order sends one email,
not two.
Call a function from your frontend
A function is public: anyone with the URL can call it. A browser page on
another domain also needs CORS headers, or the browser blocks the response.
Answer the preflight OPTIONS request and add the headers to every response:
const cors = {
"Access-Control-Allow-Origin": "https://your-site.example",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "content-type, authorization",
};
export default async function handler(req: Request) {
if (req.method === "OPTIONS") return new Response(null, { headers: cors });
return Response.json({ ok: true }, { headers: cors });
}const res = await fetch("https://<your-project>-fn.naijacloud.app/functions/v1/hello", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Ada" }),
});
const data = await res.json();Use your site's real origin rather than *, so other sites can't call the
function from their visitors' browsers.