Skip to content

Video Engine is a Node.js/Express backend powering YouTube-style video upload, processing, and streaming with JWT authentication, MongoDB storage, and Cloudinary integration.

Notifications You must be signed in to change notification settings

CodeCenturian/Video-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SaaS-Ready Backend Development Guide

1. Project Setup & Initial Configuration

1.1 Initialize Project

mkdir backend-project
cd backend-project
npm init -y

Why?

  • This creates a new project folder and initializes it with a package.json file.
  • If omitted: You won’t have a proper dependency manager, making it harder to track and install packages.

1.2 Git Setup

git init
mkdir -p src/{controllers,db,middlewares,models,routes,utils}

Why?

  • Initializes version control (Git) and sets up a folder structure.
  • If omitted: You lose version control benefits and an organized structure, which is critical for scaling SaaS projects.

Create a .gitignore file:

npx gitignore node

Why?

  • Excludes files/folders like node_modules and .env from being tracked.
  • If omitted: Sensitive data and bulky dependencies might be committed to your repository.

1.3 Install Dependencies

# Core dependencies
npm install express mongoose dotenv bcrypt jsonwebtoken multer cloudinary

# Development dependency for auto-reload
npm install -D nodemon

Why?

  • Express: Framework for handling HTTP requests.
  • Mongoose: ODM for MongoDB.
  • dotenv: Loads environment variables securely.
  • bcrypt & jsonwebtoken: For secure authentication.
  • Multer & Cloudinary: Handle file uploads and cloud storage.
  • nodemon: Automatically restarts your server during development.
  • If omitted: Missing packages can break critical functionalities like authentication, database interaction, or file uploads.

1.4 Basic File Structure

backend-project/
├── src/
│   ├── controllers/    # Contains business logic.
│   ├── db/             # Handles database connections.
│   ├── middlewares/    # Custom middleware (error handling, auth, etc.)
│   ├── models/         # Mongoose schemas and models.
│   ├── routes/         # API endpoints.
│   ├── utils/          # Helper functions and utilities.
│   └── index.js        # Express server setup.
├── .env                # Environment configuration.
├── .gitignore          # Files/folders to ignore in Git.
└── package.json        # Project dependencies and scripts.

Why?

  • An organized structure keeps your code maintainable and scalable.
  • If omitted: As your project grows, it becomes harder to manage and debug.

2. Environment Configuration

2.1 Create .env File

PORT=8000
MONGO_URI=mongodb://localhost:27017/yourdbname
ACCESS_TOKEN_SECRET=your_access_token_secret
REFRESH_TOKEN_SECRET=your_refresh_token_secret
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
JWT_SECRET=your_jwt_secret

Why?

  • Securely stores configuration and secret values outside of your code.
  • If omitted: Your application might expose sensitive information or fail in different environments.

2.2 Update package.json Scripts

"scripts": {
  "dev": "nodemon src/index.js",
  "start": "node src/index.js"
}

Why?

  • Simplifies starting your application in development (with auto-reload) and production modes.
  • If omitted: You must manually restart the server on every change, hindering productivity.

3. Database Connection Setup

**3.1 Create **src/db/connectDB.js

import mongoose from 'mongoose';
import { DB_NAME } from '../constants.js';

const connectDB = async () => {
  try {
    // Connect to MongoDB using the connection string from .env combined with DB_NAME
    const connectionInstance = await mongoose.connect(`${process.env.MONGO_URI}/${DB_NAME}`);
    console.log(`MongoDB Connected: ${connectionInstance.connection.host}`);
  } catch (error) {
    console.error("MongoDB Connection Error: ", error);
    process.exit(1); // Exit if connection fails, preventing further errors.
  }
};

export default connectDB;

Why?

  • Establishes a connection with the database to perform CRUD operations.
  • If omitted: Your application won't be able to interact with the database, making it impossible to store or retrieve data.

**3.2 Create **src/constants.js

export const DB_NAME = "yourdbname";
export const PORT = process.env.PORT || 8000;

Why?

  • Centralizes configuration for reuse across your app.
  • If omitted: You might repeat configuration details, increasing maintenance overhead.

4. Express Server Setup

**4.1 Create **src/index.js

import express from 'express';
import dotenv from 'dotenv';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import connectDB from './db/connectDB.js';
import { PORT } from './constants.js';

// Load environment variables
dotenv.config();

const app = express();

// Middleware Setup
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*',
  credentials: true
})); // Enables Cross-Origin Resource Sharing.
app.use(express.json({ limit: "16kb" })); // Parses incoming JSON requests.
app.use(express.urlencoded({ extended: true, limit: "16kb" })); // Parses URL-encoded payloads.
app.use(cookieParser()); // Parses cookies attached to the client request.
app.use(express.static("public")); // Serves static files.

connectDB()
  .then(() => {
    app.listen(PORT, () => {
      console.log(`Server running on port ${PORT}`);
    });
  })
  .catch(err => {
    console.error("MongoDB connection failed: ", err);
  });

Why?

  • Sets up the core server, middleware, and starts listening for requests.
  • If omitted: The server will not run or handle incoming requests properly.

5. User Model & Authentication Setup

5.1 Create User Model (src/models/user.model.js)

import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

const userSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true,
      index: true
    },
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true
    },
    password: {
      type: String,
      required: [true, "Password is required"]
    },
    refreshToken: {
      type: String
    }
  },
  { timestamps: true }
);

// **Pre-save hook:** Encrypts password before saving to the database.
userSchema.pre("save", async function (next) {
  if (!this.isModified("password")) return next(); // Skip if password hasn't changed.
  this.password = await bcrypt.hash(this.password, 10); // Hashes the password.
  next();
});

// **Method:** Verifies if the entered password matches the stored hash.
userSchema.methods.isPasswordCorrect = async function (password) {
  return await bcrypt.compare(password, this.password);
};

// **Method:** Generates a JWT for authentication.
userSchema.methods.generateAccessToken = function () {
  return jwt.sign(
    {
      _id: this._id,
      email: this.email,
      username: this.username
    },
    process.env.ACCESS_TOKEN_SECRET,
    { expiresIn: '1d' } // Token validity period.
  );
};

export const User = mongoose.model("User", userSchema);

Why?

  • Defines the user schema, handles password encryption, and JWT generation.
  • If omitted: Your application won't have a secure user model, leading to potential security vulnerabilities.

6. File Upload Configuration

6.1 Cloudinary Setup (src/utils/cloudinary.js)

import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET
});

// Uploads a file to Cloudinary and returns the response.
export const uploadOnCloudinary = async (localFilePath) => {
  try {
    if (!localFilePath) return null;
    const response = await cloudinary.uploader.upload(localFilePath, {
      resource_type: "auto"
    });
    return response;
  } catch (error) {
    console.error("Cloudinary upload error:", error);
    // Optionally, remove the temporary file if upload fails.
    return null;
  }
};

Why?

  • Configures Cloudinary for storing uploaded files, critical for SaaS platforms handling user media.
  • If omitted: You must handle file storage on your own server, which is less scalable and secure.

6.2 Multer Middleware (src/middlewares/multer.middleware.js)

import multer from 'multer';

// Defines storage settings for Multer.
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, './public/temp'); // Temporary storage before uploading to Cloudinary.
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + '-' + file.originalname); // Unique filename to avoid conflicts.
  }
});

export const upload = multer({ storage });

Why?

  • Handles multipart/form-data for file uploads, essential for profile pictures, documents, etc.
  • If omitted: File uploads become harder to manage and you risk overwriting files.

7. Error Handling Setup

7.1 API Response Helper (src/utils/apiResponse.js)

class ApiResponse {
  constructor(statusCode, data, message = "Success") {
    this.statusCode = statusCode;
    this.data = data;
    this.message = message;
    this.success = statusCode < 400; // Determines success based on status code.
  }
}

export { ApiResponse };

Why?

  • Standardizes API responses for consistency, making client-side handling predictable.
  • If omitted: Clients might receive inconsistent responses, complicating error handling.

7.2 Async Error Handler (src/utils/asyncHandler.js)

// Wraps async route handlers to catch errors and pass them to the error middleware.
export const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch((err) => next(err));
};

Why?

  • Ensures that errors in asynchronous code are properly caught and managed.
  • If omitted: Uncaught errors may crash your application or produce unhelpful error messages.

8. API Routes

8.1 User Routes (src/routes/user.routes.js)

import { Router } from 'express';
import { registerUser, loginUser } from '../controllers/user.controller.js';
import { upload } from '../middlewares/multer.middleware.js';

const router = Router();

// Route for user registration with file uploads (avatar/cover image)
router.post('/register',
  upload.fields([
    { name: "avatar", maxCount: 1 },
    { name: "coverImage", maxCount: 1 }
  ]),
  registerUser
);

// Route for user login
router.post('/login', loginUser);

export default router;

Why?

  • Defines endpoints for user registration and login.
  • If omitted: Your application won't expose critical functionality for user management.

8.2 Integrate Routes into Express App

Add the routes to your main server file (src/index.js or src/app.js):

import authRoutes from './routes/user.routes.js';
// ...
app.use('/api/v1/users', authRoutes);

Why?

  • Ensures that the API endpoints are available to the client.
  • If omitted: Clients won’t be able to interact with the backend API.

9. Testing Your Setup

9.1 Start the Development Server

npm run dev

Why?

  • Launches your server in development mode with hot-reload capabilities.
  • If omitted: You won't see your changes in real-time, slowing down development.

9.2 Testing with Postman

  • Register User Endpoint:
    POST request to http://localhost:8000/api/v1/users/register
    Use form-data to send:

    • username
    • email
    • password
    • avatar (file)
    • coverImage (file)
  • Login User Endpoint:
    POST request to http://localhost:8000/api/v1/users/login with JSON body:

    {
      "email": "your_email@example.com",
      "password": "your_password"
    }

Why?

  • Testing with Postman ensures that your endpoints work as expected before integration with the frontend.
  • If omitted: Bugs might go unnoticed until later stages, complicating debugging.

10. Key Concepts & Pro Tips for SaaS-Grade Applications

  1. Middleware:

    • Purpose: Processes requests (e.g., authentication, logging).
    • Without it: Code becomes monolithic and error-prone, reducing scalability.
  2. MVC Architecture:

    • Models: Define data structure (via Mongoose).
    • Controllers: Business logic.
    • Routes: Define API endpoints.
    • Without clear separation: Your code becomes messy and difficult to maintain, making future enhancements a challenge.
  3. JWT Authentication:

    • Access Token: Short-lived token to secure API endpoints.
    • Refresh Token: Used to generate new access tokens.
    • Without JWT: Your API may be vulnerable to unauthorized access.
  4. File Upload Flow:

    • Multer → Cloudinary → Database: Efficient handling of file storage, critical for SaaS applications dealing with media.
    • Without proper flow: Your application might suffer from performance issues and security vulnerabilities.
  5. Error Handling:

    • Centralized error handling makes your API responses predictable and robust.
    • Without it: Errors might crash the application or provide confusing messages to users.
  6. Security & Performance:

    • Security: Use HTTPS, store secrets in .env, and consider packages like helmet for secure headers.
    • Performance: Implement caching, pagination, and database indexing to handle high traffic efficiently.
  7. Production Readiness:

    • Logging: Use robust logging (e.g., Winston) for monitoring.
    • Process Management: Tools like PM2 ensure your application restarts on failure.
    • Without these: Your application may suffer from downtime or slow performance under load.

About

Video Engine is a Node.js/Express backend powering YouTube-style video upload, processing, and streaming with JWT authentication, MongoDB storage, and Cloudinary integration.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published