Skip to content

NinjaInDhoti/s3-nodejs-assets

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

S3 Image Storage — Node.js + AWS S3 + PostgreSQL

Secure, production-ready image upload, storage, and retrieval using Node.js, AWS S3, and PostgreSQL (via Prisma). Images are stored in S3; only the S3 key reference is saved in the database.


Features

  • Upload single or multiple images via REST API
  • Auto-resize and convert to WebP using Sharp
  • Magic-byte validation (prevents MIME spoofing)
  • Referer-locked S3 bucket policy (only your domain can access images)
  • Pre-signed URLs for private assets (1-hour TTL)
  • Polymorphic DB schema — attach images to any entity
  • Least-privilege IAM policy
  • CORS locked to your domain

Prerequisites

  • Node.js 18+
  • PostgreSQL (or swap Prisma provider for MySQL/SQLite)
  • AWS account with S3 access
  • AWS CLI configured (aws configure)

Quick Start

1. Clone and install

git clone <repo-url>
cd s3-image-app
npm install

2. Set up environment

cp .env.example .env
# Edit .env with your values

3. Set up the database

npx prisma generate
npx prisma migrate dev --name init

4. Create and configure S3 bucket

# Create bucket
aws s3api create-bucket \
  --bucket your-app-images \
  --region ap-south-1 \
  --create-bucket-configuration LocationConstraint=ap-south-1

# Apply security configs
chmod +x scripts/setup-s3.sh
./scripts/setup-s3.sh your-app-images https://yourwebsite.com

5. Run

npm run dev      # Development with hot reload
npm start        # Production

API Reference

All endpoints require Authorization: Bearer <JWT> header.

Upload Image

POST /api/images/upload
Content-Type: multipart/form-data

Fields:
  image      (file, required)  — image file, max 5MB
  folder     (string)          — S3 prefix: "avatars", "products", etc. (default: "uploads")
  alt        (string)          — alt text
  entityId   (string)          — ID of related entity
  entityType (string)          — "Product", "User", etc.

Response:

{
  "success": true,
  "image": {
    "id": "uuid",
    "s3Key": "uploads/abc123.webp",
    "url": "https://bucket.s3.region.amazonaws.com/uploads/abc123.webp",
    "width": 800,
    "height": 600,
    "sizeBytes": 42000,
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
}

Upload Multiple Images

POST /api/images/upload-many
Content-Type: multipart/form-data

Fields:
  images[]   (files, required)  — up to 10 images
  folder     (string)
  entityId   (string)
  entityType (string)

Get Image Record

GET /api/images/:id

Returns DB record with a fresh pre-signed URL.

Get Pre-signed URL

GET /api/images/:id/url?expiresIn=3600

Get Images by Entity

GET /api/images/entity/:entityType/:entityId

Delete Image

DELETE /api/images/:id

Deletes from both S3 and the database.


S3 Security Configuration

Bucket Policy (Referer Lock)

The config/s3-bucket-policy.json restricts GetObject to requests with your domain in the Referer header. Direct URL access, curl, and hotlinking from other sites all return 403 Forbidden.

Replace YOUR-BUCKET-NAME and domain before applying.

aws s3api put-bucket-policy \
  --bucket your-app-images \
  --policy file://config/s3-bucket-policy.json

CORS

aws s3api put-bucket-cors \
  --bucket your-app-images \
  --cors-configuration file://config/s3-cors.json

IAM Policy

Attach config/iam-policy.json to your EC2 instance role or ECS task role. This grants only the minimum permissions needed (PutObject, GetObject, DeleteObject, HeadObject, ListBucket).

In production, never use root credentials or hardcoded keys.


Public vs Private Assets

Use Case Strategy
Product images, blog covers Store in public/ prefix; serve via bucket URL; protected by Referer policy
User avatars, invoices, documents Store in private/ prefix; always generate a fresh pre-signed URL

Deployment

Local Development

Use AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY in .env with a dev IAM user.

EC2 / ECS / Lambda

Attach an IAM Role to your instance or task. Remove the key env vars — the AWS SDK auto-discovers credentials from the metadata service.

Docker

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npx prisma generate
EXPOSE 3000
CMD ["node", "src/index.js"]

Build Commands

npm install          # Install dependencies
npm run db:generate  # Generate Prisma client
npm run db:migrate   # Run DB migrations
npm run dev          # Start dev server (nodemon)
npm start            # Start production server

Project Structure

s3-image-app/
├── src/
│   ├── index.js              # Express app entry point
│   ├── services/
│   │   └── s3Service.js      # S3 upload, pre-signed URL, delete
│   ├── routes/
│   │   └── images.js         # REST endpoints
│   ├── middleware/
│   │   └── auth.js           # JWT authentication
│   └── db/
│       └── index.js          # Prisma client singleton
├── prisma/
│   └── schema.prisma         # DB schema
├── config/
│   ├── s3-bucket-policy.json # S3 bucket policy (Referer lock)
│   ├── s3-cors.json          # CORS configuration
│   └── iam-policy.json       # IAM least-privilege policy
├── scripts/
│   └── setup-s3.sh           # One-command S3 setup script
├── .env.example
├── .gitignore
└── package.json

License

MIT

About

A practical guide for backend developers: upload images to S3, store references in a database, generate secure pre-signed URLs, and configure bucket policies so only your hosted website can access assets.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors