Uplo
Server

Hono

Uplo ships with a framework-agnostic route handler that returns a standard fetch handler — perfect for Hono on any runtime (Node, Bun, Cloudflare Workers, Deno).

npm i @uplo/node hono
import { Hono } from 'hono'
import { createUplo } from '@uplo/node'
import { createUploRouteHandler } from '@uplo/server/route-handler'
import { createPrismaAdapter } from '@uplo/adapter-prisma'
import { createS3Service } from '@uplo/service-s3'

const uplo = createUplo({
  config: {
    privateKey: process.env.APPLICATION_SECRET,
    signedIdExpiresIn: 60 * 60,
  },
  adapter: createPrismaAdapter({ prisma }),
  services: {
    s3: createS3Service({
      isPublic: false,
      region: process.env.AWS_REGION,
      bucket: process.env.AWS_BUCKET,
      accessKeyId: '*****',
      secretAccessKey: '*****',
    }),
  },
  attachments: {},
})

const app = new Hono()
const uploHandler = createUploRouteHandler({ uplo })

app.all('/uplo/*', (c) => uploHandler(c.req.raw))

export default app

You can change the mount path with the basePath option:

const uploHandler = createUploRouteHandler({ uplo, basePath: '/api/uplo' })

Routes

  • POST /uplo/create-direct-upload: creates a blob and returns a Signed ID with upload URL and headers. Returns 201, or 422 with { success: false, error: { message } } for invalid requests, unknown attachments, failed validation or disabled direct uploads.

Authentication

The route handler has no authentication, so anyone who can reach it can upload files to your bucket. Add your own middleware before it:

app.use('/uplo/*', async (c, next) => {
  const user = await getCurrentUser(c) // your auth
  if (!user) {
    return c.json({ success: false, error: { message: 'Unauthorized' } }, 401)
  }
  await next()
})

app.all('/uplo/*', (c) => uploHandler(c.req.raw))

Uploading alone does not attach a file to a record. Check that the user can change the record before calling attachSignedFile.

On this page