Production-ready RESTful API for Photo Browser Application with JWT authentication, cloud image storage, and full CRUD operations. Built with Node.js, Express, TypeScript, MongoDB Atlas, and Cloudinary.
- Runtime: Node.js 22 LTS
- Framework: Express.js 4.18
- Language: TypeScript 5.3
- Database: MongoDB Atlas (Cloud NoSQL)
- ODM: Mongoose 8.0
- JWT: JSON Web Tokens for stateless authentication
- bcryptjs: Password hashing with salt rounds
- helmet: Security headers
- cors: Cross-origin resource sharing
- express-rate-limit: API rate limiting to prevent abuse
- Zod: Runtime request validation and type-safe schemas
- Cloudinary: Cloud image storage and CDN
- Multer: Multipart form-data handling
- Sharp: Image optimization and thumbnail generation
- nodemon: Auto-reload on file changes
- ts-node: Run TypeScript directly
- Morgan: HTTP request logger
- dotenv: Environment variable management
- Node.js 22 or higher
- MongoDB Atlas account (free tier available)
- Cloudinary account (free tier available)
- npm or yarn package manager
npm installCreate a .env file in the root directory:
cp .env.example .envEdit .env and add your MongoDB Atlas connection string:
PORT=5001
NODE_ENV=development
MONGODB_URI=mongodb+srv://username:[email protected]/photo-browser?retryWrites=true&w=majority
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_EXPIRES_IN=7d
CLIENT_URL=http://localhost:3000
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret- Create account at https://cloudinary.com
- Go to Dashboard to get credentials
- Add Cloud Name, API Key, and API Secret to
.env
- Create account at https://www.mongodb.com/cloud/atlas
- Create a free M0 cluster
- Create database user with read/write permissions
- Whitelist your IP address (use 0.0.0.0/0 for development)
- Get connection string and add to
.env
npm run seedThis will populate your database with:
- 10 users from JSONPlaceholder (password:
demo123for all) - 100 albums
- 1000 photos
npm run devServer will run on http://localhost:5001
GET /health - Server health status
POST /api/auth/register # Register new user
POST /api/auth/login # Login user
GET /api/auth/me # Get current user (Protected)
GET /api/photos?_page=1&_limit=18 # List photos with pagination
GET /api/photos/:id # Get single photo
POST /api/photos # Upload photo (Protected)
PUT /api/photos/:id # Update photo (Protected)
DELETE /api/photos/:id # Delete photo (Protected)
GET /api/albums?_page=1&_limit=18 # List albums with pagination
GET /api/albums/:id # Get single album
GET /api/albums/:albumId/photos # Get photos by album
POST /api/albums # Create album (Protected)
PUT /api/albums/:id # Update album (Protected)
DELETE /api/albums/:id # Delete album (Protected)
GET /api/users/:id # Get user profile
POST http://localhost:5001/api/auth/register
Content-Type: application/json
{
"name": "John Doe",
"email": "[email protected]",
"username": "johndoe",
"password": "password123",
"phone": "555-1234", // Optional
"website": "https://john.com" // Optional
}POST http://localhost:5001/api/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "password123"
}Response:
{
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "676c...",
"name": "John Doe",
"email": "[email protected]",
"username": "johndoe"
}
}Add the JWT token to the Authorization header:
Authorization: Bearer YOUR_JWT_TOKENAccess the interactive API documentation at:
Local Development:
http://localhost:5001/api-docs
Production:
https://your-app.onrender.com/api-docs
- Try it out: Test all endpoints directly from the browser
- Request/Response Examples: See example data for all endpoints
- Authentication: Test authenticated endpoints with JWT tokens
- Schema Definitions: View all data models and validation rules
- Response Codes: See all possible HTTP status codes
- Open
http://localhost:5001/api-docsin your browser - Click on any endpoint to expand it
- Click "Try it out" to test the endpoint
- For protected endpoints:
- First, use
/api/auth/loginto get a JWT token - Click "Authorize" button at the top
- Enter:
Bearer YOUR_TOKEN_HERE - Click "Authorize"
- First, use
- Fill in the required parameters
- Click "Execute" to make the request
- View the response in the interface
Endpoint: POST /api/photos
Authentication: Required
Content-Type: multipart/form-data
# Using HTTPie
http -f POST http://localhost:5001/api/photos \
"Authorization:Bearer YOUR_TOKEN" \
image@/path/to/photo.jpg \
title="Beautiful Sunset" \
albumId=1
# Using curl
curl -X POST http://localhost:5001/api/photos \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "image=@/path/to/photo.jpg" \
-F "title=Beautiful Sunset" \
-F "albumId=1"Features:
- Automatic image optimization (max 800x800px)
- Thumbnail generation (150x150px)
- Cloud storage via Cloudinary
- Supported formats: jpeg, jpg, png, gif, webp
- Max file size: 5MB
# Login
http POST http://localhost:5001/api/auth/login \
[email protected] \
password=demo123
# Save token
TOKEN="your_token_here"
# Get photos
http GET http://localhost:5001/api/photos
# Upload photo
http -f POST http://localhost:5001/api/photos \
"Authorization:Bearer $TOKEN" \
image@~/photo.jpg \
title="My Photo"
# Update photo
http PUT http://localhost:5001/api/photos/1001 \
"Authorization:Bearer $TOKEN" \
title="Updated Title"
# Delete photo
http DELETE http://localhost:5001/api/photos/1001 \
"Authorization:Bearer $TOKEN"
# Create album
http POST http://localhost:5001/api/albums \
"Authorization:Bearer $TOKEN" \
title="My Album"
# Delete album
http DELETE http://localhost:5001/api/albums/101 \
"Authorization:Bearer $TOKEN"# Health check
curl http://localhost:5001/health
# Get photos with pagination
curl "http://localhost:5001/api/photos?_page=1&_limit=18"
# Login
curl -X POST http://localhost:5001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"demo123"}'
# Upload photo
curl -X POST http://localhost:5001/api/photos \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "[email protected]" \
-F "title=Sunset"photo-browser-app-backend/
βββ src/
β βββ config/
β β βββ database.ts # MongoDB connection
β β βββ cloudinary.ts # Cloudinary configuration
β β βββ swagger.ts # Swagger/OpenAPI configuration
β βββ models/
β β βββ User.ts # User schema & model
β β βββ Album.ts # Album schema & model
β β βββ Photo.ts # Photo schema & model
β βββ controllers/
β β βββ authController.ts # Register, login, get current user
β β βββ photoController.ts # Photo CRUD operations
β β βββ albumController.ts # Album CRUD operations
β β βββ userController.ts # User profile operations
β βββ routes/
β β βββ auth.ts # Auth routes
β β βββ photos.ts # Photo routes
β β βββ albums.ts # Album routes
β β βββ users.ts # User routes
β βββ middleware/
β β βββ auth.ts # JWT authentication
β β βββ errorHandler.ts # Global error handling
β β βββ rateLimiter.ts # Rate limiting configuration
β β βββ validateRequest.ts # Zod validation middleware
β β βββ upload.ts # Multer file upload config
β βββ schemas/
β β βββ authSchemas.ts # Auth validation schemas
β β βββ photoSchemas.ts # Photo validation schemas
β β βββ albumSchemas.ts # Album validation schemas
β βββ scripts/
β β βββ seedDatabase.ts # Seed from JSONPlaceholder
β βββ server.ts # Express app entry point
βββ .env # Environment variables (git ignored)
βββ .env.example # Environment template
βββ .gitignore # Git ignore file
βββ package.json # Dependencies & scripts
βββ tsconfig.json # TypeScript config
βββ README.md # This file
npm run dev # Start development server with nodemon
npm run build # Compile TypeScript to JavaScript
npm start # Run production server
npm run seed # Seed database with JSONPlaceholder dataAPI rate limiting is implemented to prevent abuse and ensure fair usage:
- General API Routes: 100 requests per 15 minutes
- Authentication Routes (login/register): 5 requests per 15 minutes
- Upload Routes: 10 requests per hour
When rate limit is applied, the following headers are included:
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 1640000000
{
"error": "Too many requests from this IP, please try again later.",
"retryAfter": "15 minutes"
}Status Code: 429 Too Many Requests
All API endpoints validate incoming requests using Zod schemas for type-safe runtime validation.
- Authentication: Email format, password length, username constraints
- Photos: Title length, valid URLs, numeric IDs
- Albums: Title requirements, user associations
- Query Parameters: Pagination limits, valid sort fields, numeric filters
When validation fails, you'll receive a detailed error response:
{
"error": "Validation failed",
"details": [
{
"field": "email",
"message": "Invalid email address"
},
{
"field": "password",
"message": "Password must be at least 6 characters"
}
]
}Status Code: 400 Bad Request
name: 2-100 charactersemail: Valid email formatusername: 3-30 characters, alphanumeric and underscores onlypassword: 6-100 characters
title: 1-200 characters (required)albumId: Valid numeric ID (required)
{
"error": "User with this email or username already exists"
}Status: 409 Conflict
{
"error": "Invalid credentials"
}Status: 401 Unauthorized
{
"error": "No token provided"
}Status: 401 Unauthorized
{
"error": "Photo not found"
}Status: 404 Not Found
_limit: 1-100 (default: 18)_page: Positive integer (default: 1)sort: One oftitle,createdAt,updatedAtorder: Eitherascordesc
Update your frontend .env file:
REACT_APP_BACKENDURL=http://localhost:5001/apiThen restart your frontend application.
- JWT Authentication - Secure user registration and login
- Password Hashing - bcrypt with 10 salt rounds
- Image Upload - Cloudinary integration with Sharp optimization
- Full CRUD Operations - Create, Read, Update, Delete for photos and albums
- Ownership Verification - Users can only modify their own content
- Image Processing - Automatic resize (800x800) and thumbnail generation (150x150)
- Cloud Storage - Cloudinary for image hosting and CDN delivery
- Pagination Support - Efficient data loading with
_pageand_limitparams - Database Seeding - Populate with JSONPlaceholder data
- TypeScript - Full type safety and compile-time error checking
- Security Headers - Helmet middleware for protection
- CORS Configuration - Cross-origin resource sharing
- Rate Limiting - Protection against API abuse with configurable limits
- Request Validation - Zod schemas for runtime data validation
- Advanced Search - Search photos and albums by title
- Filtering - Filter by userId, albumId
- Sorting - Sort by title, createdAt, updatedAt (asc/desc)
- Request Logging - Morgan for HTTP request tracking
- Rate Limiting - Protection against API abuse
- Request Validation - Zod schemas for runtime data validation
- Centralized Error Handling - Consistent error responses
- Swagger Documentation - Interactive API docs at
/api-docs
- Stateless Authentication - JWT tokens (7-day expiration)
- Protected Routes - Middleware-based auth verification
- Password Security - Never stored in plain text
- Ownership Checks - Users can only delete/update own content
- File Validation - Type and size restrictions on uploads
- Cloud Cleanup - Automatic deletion from Cloudinary when photos removed
- Cascade Protection - Albums with photos cannot be deleted
npm run build- Push code to GitHub
- Create account at https://render.com
- Create new Web Service
- Connect GitHub repository
- Set build command:
npm install && npm run build - Set start command:
npm start - Add environment variables from
.env
After deployment, update frontend:
REACT_APP_BACKENDURL=https://your-app.onrender.com/api- JWT Authentication: Stateless token-based auth with configurable expiration
- bcryptjs: Secure password hashing with salt rounds
- Helmet: Security headers for Express
- CORS: Configured for specific origins
- Ownership Verification: Users can only modify their own resources
- File Validation: Type and size checks on uploads
- Environment Variables: Sensitive data protection
- TypeScript Strict Mode: Compile-time error prevention
- MongoDB Atlas integration
- User authentication (register, login)
- JWT middleware and protected routes
- Image upload with Cloudinary
- Full CRUD operations for photos
- Full CRUD operations for albums
- Image optimization with Sharp
- Ownership verification
- Database seeding script
- Search photos by title
- Filter by album/user
- Sort by date/popularity
- Refresh token implementation
- Rate limiting
- Request validation with Zod
- Comprehensive error handling middleware
- Unit and integration tests
- API documentation with Swagger
- Pagination metadata (total count, pages)
Mahbub Alam Khan
MIT
Ensure your package.json has the correct build and start scripts (already configured):
{
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "nodemon src/server.ts"
}
}- Go to https://render.com
- Sign up with your GitHub account
- Connect your GitHub repository
- Click "New +" β "Web Service"
- Connect to your GitHub repository:
photo-browser-app-backend - Configure the service:
- Name:
photo-browser-backend - Environment:
Node - Build Command:
npm install && npm run build - Start Command:
npm start - Instance Type: Free
- Name:
In Render dashboard, add all environment variables from your .env:
PORT=5000
NODE_ENV=production
MONGODB_URI=mongodb+srv://username:[email protected]/photo-browser?retryWrites=true&w=majority
JWT_SECRET=your-production-jwt-secret-here
JWT_EXPIRES_IN=7d
CLIENT_URL=https://your-frontend-domain.com
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
Important Security Notes:
- Use a strong, unique
JWT_SECRETfor production - Update
CLIENT_URLwith your deployed frontend URL - Ensure MongoDB Atlas Network Access allows Render's IP addresses
- Never commit
.envfile to GitHub
- Click "Create Web Service"
- Render will automatically build and deploy your app
- Your API will be available at:
https://your-app-name.onrender.com
Test the health endpoint:
curl https://your-app-name.onrender.com/healthExpected response:
{
"status": "OK",
"message": "Photo Browser API is running"
}Update your frontend .env to point to the deployed backend:
REACT_APP_BACKENDURL=https://your-app-name.onrender.com/apiheroku create photo-browser-backend
heroku config:set MONGODB_URI=your-mongodb-uri
heroku config:set JWT_SECRET=your-jwt-secret
# Add other environment variables
git push heroku main- Go to https://railway.app
- Connect GitHub repository
- Add environment variables
- Deploy automatically
- Launch EC2 instance with Node.js
- Clone repository
- Install dependencies
- Set up PM2 for process management
- Configure Nginx as reverse proxy
-
Network Access:
- Remove
0.0.0.0/0(development wildcard) - Add Render/Heroku IP addresses only
- Remove
-
Database Users:
- Use strong, unique passwords
- Limit privileges to specific database
-
Monitoring:
- Enable MongoDB Atlas monitoring
- Set up alerts for connection issues
Render automatically deploys when you push to main branch:
git add .
git commit -m "Update feature"
git push origin mainRender will:
- Detect the push
- Run build command
- Deploy new version
- Zero downtime deployment
Contributions, issues, and feature requests are welcome!
- Express.js Documentation
- MongoDB Atlas Docs
- Mongoose Documentation
- Cloudinary Documentation
- Render Deployment Guide
- JWT Best Practices
Created: December 29, 2025
Last Updated: December 29, 2025