Skip to main content
A Node.js application on Sevalla stores files in an object storage bucket through the bucket’s S3 API: the AWS SDK for JavaScript reads the bucket’s endpoint, name and keys from environment variables, uploads files as they arrive, and hands out presigned URLs to download them. Files in a bucket survive deployments and are shared by every instance, unlike files on an instance’s disk. This guide adds file routes to an Express application. The client code works the same in any Node.js framework.

Before you begin

  • An Express application deployed on Sevalla, as in Deploy Express.
  • Node.js 20 or later in the application, which the AWS SDK for JavaScript (@aws-sdk/client-s3) needs. The Express guide pins Node.js 24.

Create a bucket

In the sidebar, click Object storage, then click Create object storage. Enter storefront-media as the Name, choose the Location closest to the application, and click Create object storage. See Create a bucket for every field.

Add the bucket details to the application

1

Copy the values

On the bucket’s Settings tab, copy the Endpoint and Bucket name under Details, and the Access key and Secret key under Access keys.
2

Add them as environment variables

On the application’s Environment variables tab, click Add environment variables. Add these variables, keep Available during runtime selected, and click Add environment variables:They apply at the next deployment.
See Connect with the S3 API for what each value is.

Install the AWS SDK

  • @aws-sdk/client-s3 sends the S3 requests.
  • @aws-sdk/lib-storage streams uploads and splits large files into parts.
  • @aws-sdk/s3-request-presigner creates presigned URLs.

Create the client

Create one client for the whole application:
storage.js
Sevalla buckets run on Cloudflare R2, which ignores the region, but the SDK requires one: use auto. The two WHEN_REQUIRED options stop the SDK from adding checksums that only some operations need: without them, it signs the checksum of an empty body into presigned upload URLs, and R2 rejects the real upload.

Add routes for files

Add three routes to server.js: one that streams an upload into the bucket, one that lists the files, and one that redirects to a presigned URL for a file:
server.js
These routes have no authentication: anyone who knows the address can upload files and read them. Check who’s signed in before you upload or sign URLs in a real application.
To try it locally, put the four variables in your shell (for example with export S3_ENDPOINT=...), run npm start, and upload a file with the curl command in Deploy and verify, using http://localhost:3000.

Deploy and verify

Commit and push the changes. With auto-deploy on, the push starts a deployment; otherwise, start one with Deploy now on the application’s Overview tab. When the deployment’s status changes to Deployed:
  1. Upload a file to the application’s domain:
    The response is {"key":"uploads/logo.png"}.
  2. Open https://storefront-x7k2m.sevalla.app/files/logo.png in a browser. The application redirects to a presigned URL on the bucket’s endpoint, and the image opens.
  3. On the bucket’s Objects tab, open the uploads folder. logo.png is there.
If a request fails, read the application’s runtime logs for the S3 error, then check the connection troubleshooting table. The most common cause is the display name in S3_BUCKET instead of the Bucket name.

Let browsers upload directly

Uploads that pass through the application use its bandwidth and memory. To let the browser send a file straight to the bucket, create a presigned URL for PutObjectCommand on the server and give it to the browser, and add a CORS rule to the bucket for your site’s origin. See Share files with presigned URLs.

Serve files publicly

For files that anyone may read, such as product images, turn on public access on the bucket’s Settings tab and link to the object’s public URL instead of signing one each time. Cloudflare’s CDN caches public files. See Public URLs.