Skip to content

Hono on Cloudflare with SST

Create and deploy a Hono API on Cloudflare with SST.

We are going to build an API with Hono, add an R2 bucket for file uploads, and deploy it using Cloudflare with SST.

Before you get started:

  1. Create your Cloudflare API token
  2. Install the SST CLI

1. Create a project

Let’s start by creating our app.

Terminal window
mkdir my-hono-api && cd my-hono-api
npm init -y

Init SST

Now let’s initialize SST in our app. Make sure you have the CLI installed.

Terminal window
sst init

This’ll create a sst.config.ts file in your project root.

Set the Cloudflare API token

You can save your Cloudflare API token in a .env file or just set it directly.

Terminal window
export CLOUDFLARE_API_TOKEN=aaaaaaaa_aaaaaaaaaaaa_aaaaaaaa

2. Add a Worker

Let’s add a Worker. Update your sst.config.ts.

async run() {
const hono = new sst.cloudflare.Worker("Hono", {
url: true,
handler: "index.ts",
return {
api: hono.url,

We are enabling the Worker URL, so we can use it as our API.

3. Add an R2 Bucket

Let’s add an R2 bucket for file uploads. Update your sst.config.ts.

const bucket = new sst.cloudflare.Bucket("MyBucket");

Now, link the bucket to our Worker.

const hono = new sst.cloudflare.Worker("Hono", {
url: true,
link: [bucket],
handler: "index.ts",

4. Upload a file

We want the / route of our API to upload a file to the R2 bucket. Create an index.ts file and add the following.

const app = new Hono()
.put("/*", async (c) => {
const key = crypto.randomUUID();
await Resource.MyBucket.put(key, c.req.raw.body, {
httpMetadata: {
contentType: c.req.header("content-type"),
return new Response(`Object created with key: ${key}`);
export default app;

Add the imports.

import { Hono } from "hono";
import { Resource } from "sst";

And install the npm packages.

Terminal window
npm install hono

5. Download a file

We want to download the last uploaded file if you make a GET request to the API. Add this to your routes in index.ts.

const app = new Hono()
// ...
.get("/", async (c) => {
const first = await Resource.MyBucket.list().then(
(res) =>
(a, b) => a.uploaded.getTime() - b.uploaded.getTime(),
const result = await Resource.MyBucket.get(first.key);
c.header("content-type", result.httpMetadata.contentType);
return c.body(result.body);

We are getting a list of the files in the files in the bucket with Resource.MyBucket.list() and we are getting a file for the given key with Resource.MyBucket.get().

Start dev mode

Start your app in dev mode.

Terminal window
sst dev

This will give you the URL of your API.


Test your app

Let’s try uploading a file from your project root. Make sure to use your API URL.

Terminal window
curl --upload-file package.json

Now head over to in your browser and it’ll load the file you just uploaded.

6. Deploy your app

Finally, let’s deploy your app!

Terminal window
sst deploy