Deleting Files from S3
When a user deletes a resource (a profile photo, a document, etc.), you need to delete the corresponding object from S3 as well — otherwise your bucket fills up with orphaned files.
Delete a Single Object
utils/deleteFromS3.js
import { DeleteObjectCommand } from "@aws-sdk/client-s3";
import s3 from "../config/s3.js";
export async function deleteFromS3(key) {
const command = new DeleteObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: key,
});
await s3.send(command);
}
note
S3's DeleteObject does not throw an error if the key doesn't exist. It silently succeeds, which is fine — idempotent deletes are generally what you want.
Delete Multiple Objects
If you need to delete several files at once (e.g., deleting all attachments for a post), use DeleteObjectsCommand to batch them in a single API call — much more efficient than looping.
utils/deleteFromS3.js
import {
DeleteObjectCommand,
DeleteObjectsCommand,
} from "@aws-sdk/client-s3";
import s3 from "../config/s3.js";
export async function deleteFromS3(key) {
const command = new DeleteObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: key,
});
await s3.send(command);
}
export async function deleteManyFromS3(keys) {
if (keys.length === 0) return;
const command = new DeleteObjectsCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME,
Delete: {
Objects: keys.map((key) => ({ Key: key })),
Quiet: true, // Don't return the list of deleted keys in the response
},
});
const result = await s3.send(command);
if (result.Errors && result.Errors.length > 0) {
console.error("Some files could not be deleted:", result.Errors);
}
}
Delete Route in Express
routes/files.js
import express from "express";
import { deleteFromS3, deleteManyFromS3 } from "../utils/deleteFromS3.js";
import FileModel from "../models/file.js";
const router = express.Router();
// Delete a single file
router.delete("/:id", async (req, res) => {
try {
const file = await FileModel.findById(req.params.id);
if (!file) {
return res.status(404).json({ message: "File not found" });
}
// Delete from S3 first, then remove from database
await deleteFromS3(file.key);
await FileModel.findByIdAndDelete(req.params.id);
res.json({ message: "File deleted successfully" });
} catch (error) {
res.status(500).json({ message: "Failed to delete file", error: error.message });
}
});
// Delete multiple files
router.delete("/", async (req, res) => {
const { ids } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({ message: "Provide an array of file IDs" });
}
try {
const files = await FileModel.find({ _id: { $in: ids } });
if (files.length === 0) {
return res.status(404).json({ message: "No files found" });
}
const keys = files.map((f) => f.key);
await deleteManyFromS3(keys);
await FileModel.deleteMany({ _id: { $in: ids } });
res.json({ message: `${files.length} file(s) deleted successfully` });
} catch (error) {
res.status(500).json({ message: "Failed to delete files", error: error.message });
}
});
export default router;
Delete on User Account Removal
A common pattern is to clean up all of a user's files when they delete their account:
routes/users.js
import { deleteManyFromS3 } from "../utils/deleteFromS3.js";
import FileModel from "../models/file.js";
import UserModel from "../models/user.js";
router.delete("/:id", async (req, res) => {
try {
const files = await FileModel.find({ userId: req.params.id });
const keys = files.map((f) => f.key);
if (keys.length > 0) {
await deleteManyFromS3(keys);
}
await FileModel.deleteMany({ userId: req.params.id });
await UserModel.findByIdAndDelete(req.params.id);
res.json({ message: "Account and all associated files deleted" });
} catch (error) {
res.status(500).json({ message: "Failed to delete account" });
}
});
Key Takeaways
- Always delete from S3 before removing the record from your database — if the DB delete fails, you can retry; if you delete the DB record first, you lose the S3 key
- Use
DeleteObjectsCommandto batch-delete multiple files in one API call - S3 deletes are idempotent — deleting a non-existent key doesn't throw an error