← Back
aws express react s3

Upload files to s3 with express and react

Bucket #

CORS (change allowed origins, at least!):

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "POST",
            "GET",
            "PUT",
            "DELETE",
            "HEAD"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "ETag"
        ]
    }
]

I had to unblock public access (on development)

Express server #

A service to create a signed url:

api/service.ts:

import * as z from "zod";
import { v4 as uuid } from "uuid";
import AWS from "aws-sdk";

const bucket = process.env.AWS_BUCKET;

const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
});

const RequestCreateUploadUrlSchema = z.object({
key: z.string(),
contentType: z.string(),
});

export type RequestCreateUploadUrl = z.infer<
typeof RequestCreateUploadUrlSchema
>;

export async function createUploadUrl(data: any) {
const { key, contentType } = RequestCreateUploadUrlSchema.parse(data);

const url = s3.getSignedUrl("putObject", {
Bucket: bucket,
Key: key,
ContentType: contentType,
});

return {
url,
key,
};
}

React upload #

async function createUploadUrl(
fileName: string,
fileType: string
): Promise<string> {
const key = `public/${fileName}`;
const result = await ky
.post(`${URL}/uploads`, {
json: { key, fileType },
})
.json();
return (result as any).url as string;
}

async function upload() {
const s3Url = await createUploadUrl("files/my-video.mp4", "video/mp4");
const blob = await ky.get(mediaBlobUrl).blob();
const file = new File([blob], "my-video.mp4", {
type: "video/mp4",
});
const response = await ky.put(s3Url, { body: file });
return response;
}

Express boilerplate #

api/routes.ts:

import { Router } from "express";
import { createUploadUrl } from "./service";

const routes = Router();

routes.post("/uploads", (req, res, next) =>
createUploadUrl(req.body)
.then((response) => res.json(response))
.catch(next)
);

export default routes;

api/app.ts:

import express, { Application, Request, Response, NextFunction } from "express";
import cors from "cors";
import helmet from "helmet";
import bodyParser from "body-parser";
import { CORS_ORIGIN, isDevelopment } from "./config";
import routes from "./routes";

const app: Application = express();
app.use(helmet());
app.use(
cors({
origin: CORS_ORIGIN,
})
);
app.use(bodyParser.json());

app.use("/", routes);

app.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (isDevelopment) {
console.error("ERROR", err.toString());
console.error("STACK", err.stack);
}
res.status(err.status || 500).send(err.toString());
});

export default app;

api/config.ts:

import dotenv from "dotenv";

dotenv.config();

const NODE_ENV = process.env.NODE_ENV;

export const ENV =
NODE_ENV === "production"
? "production"
: NODE_ENV === "test"
? "test"
: "development";

export const CORS_ORIGIN = process.env.CORS_ORIGIN || "*";

export const PORT = parseInt(process.env.PORT || "3000");

export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "";
export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "";
export const AWS_REGION = process.env.AWS_REGION || "";
export const AWS_BUCKET = process.env.AWS_BUCKET || "";

api/server.ts:

import http from "http";
import app from "./app";
import { PORT } from "./config";

const server = http.createServer(app);

server.listen(PORT, () => {
return console.log(`Server is listening on http://localhost:${PORT}`);
});

process.on("SIGTERM", () => {
console.log("Closing...");
server.close(() => {
console.log(`Bye`);
});
});