Mastering Image Uploads in Node.js: A Beginner-to-Advanced Guide with Multer and Cloudinary

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Mastering Image Uploads in Node.js: A Beginner-to-Advanced Guide with Multer and Cloudinary

    Efficiently handling image uploads is a critical aspect of backend development, particularly in modern web applications. In this comprehensive guide, we'll demonstrate how to store images using Node.js, TypeScript, PostgreSQL, Multer, and Cloudinary. Whether you're a beginner or looking to enhance your backend skills, this step-by-step tutorial will help you seamlessly implement image uploads.





    Prerequisites

    Before diving into the implementation, ensure you have the following:

    1. Node.js and npm/yarn installed.
    2. Basic knowledge of TypeScript and Express.js.
    3. A PostgreSQL database instance.
    4. A Cloudinary account for image hosting.





    1. Initializing the Project

    Start by creating a new Node.js project:






    npm init -y







    Install the required dependencies:






    npm install cloudinary dotenv multer pg cors express
    npm install --save-dev typescript ts-node eslint nodemon typescript-eslint @eslint/js @types/express @types/multer @types/pg @types/cors







    Create a .env file to store your environment variables:






    touch .env







    Populate the .env file with the following:






    PORT=8080
    DB_USER=your_db_user
    DB_PASSWORD=your_db_password
    DB_HOST=localhost
    DB_PORT=5432
    DB_NAME=your_db_name
    CLOUDINARY_CLOUD_NAME=your_cloud_name
    CLOUDINARY_API_KEY=your_api_key
    CLOUDINARY_API_SECRET=your_api_secret










    2. Configuring TypeScript

    Set up TypeScript by creating a tsconfig.json file:






    touch tsconfig.json







    Add the following configuration:






    {
    "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "target": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist",
    "strict": true
    },
    "lib": ["es2015"]
    }










    3. Setting Up the Express Server

    Create the necessary directories:






    mkdir src public
    cd public && mkdir temp && cd temp && touch .gitkeep
    cd ../src && mkdir controllers db middlewares routes utils







    Inside the src directory, create app.ts and index.ts:


    src/app.ts






    import express from 'express';
    import cors from 'cors';
    import routes from './routes';

    const app = express();

    app.use(
    cors({
    origin: process.env.CORS_ORIGIN,
    credentials: true,
    })
    );
    app.use(express.json({ limit: '16kb' }));
    app.use(express.urlencoded({ extended: true, limit: '16kb' }));
    app.use(express.static('public'));

    app.use('/api/v1', routes);

    export default app;







    src/index.ts






    import app from './app';

    app.listen(process.env.PORT || 8080, () => {
    console.log(`Server running on port ${process.env.PORT}!`);
    });










    4. Configuring PostgreSQL

    Set up database connectivity in src/db/db.ts:






    import { Pool } from 'pg';
    import dotenv from 'dotenv';

    // Load environment variables from .env file
    dotenv.config();

    const pool = new Pool({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    password: process.env.DB_PASSWORD,
    port: Number(process.env.DB_PORT),
    });

    pool.connect(err => {
    if (err) {
    console.error('Error connecting to the database:', err);
    } else {
    console.log('Connected to PostgreSQL database');
    }
    });

    export default pool;










    5. Setting Up Multer Middleware

    Handle file uploads using Multer by creating src/middlewares/multer.middleware.ts:






    import multer from 'multer';

    const storage = multer.diskStorage({
    destination: function (req, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) {
    cb(null, './public/temp');
    },
    filename: function (req, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, file.fieldname + '-' + uniqueSuffix);
    },
    });

    const upload = multer({ storage });

    export default upload;










    6. Integrating Cloudinary

    Create src/utils/cloudinary.ts:






    import { v2 as cloudinary } from 'cloudinary';
    import fs from 'fs';

    (async function () {
    // Configure Cloudinary
    cloudinary.config({
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_API_KEY,
    api_secret: process.env.CLOUDINARY_API_SECRET,
    secure: true,
    });
    })();

    const uploadOnCloudinary = async (file: string) => {
    try {
    if (!file) return null;

    // Upload image to Cloudinary
    const result = await cloudinary.uploader.upload(file, {
    folder: 'ryde-uber-clone',
    resource_type: 'image',
    });

    // file has been uploaded successfully
    console.log('file is uploaded on cloudinary ', result);

    // Check if the file exists before attempting to delete it
    if (fs.existsSync(file)) {
    fs.unlinkSync(file);
    } else {
    console.warn(`File not found: ${file}`);
    }

    return result.url;
    } catch (error) {
    console.error('Error uploading to Cloudinary:', error);

    // Check if the file exists before attempting to delete it
    if (fs.existsSync(file)) {
    fs.unlinkSync(file);
    } else {
    console.warn(`File not found: ${file}`);
    }

    return null;
    }
    };

    export default uploadOnCloudinary;










    7. Creating Routes and Controllers

    Define a route for handling uploads:


    src/routes/auth.routes.ts






    import express from 'express';
    import { signUp } from '../controllers/auth.controllers';
    import upload from '../middlewares/multer.middleware';

    const router = express.Router();

    router.post('/sign-up', upload.single('avatar'), signUp); // User signUp

    export default router;







    src/routes/index.ts






    import express from 'express';
    import authRoutes from './auth.routes';

    const router = express.Router();

    router.use('/auth', authRoutes);

    export default router;







    src/controllers/auth.controllers.ts






    import { Request, Response } from 'express';
    import pool from '../db/db';
    import asyncHandler from '../utils/asyncHandler';
    import uploadOnCloudinary from '../utils/cloudinary';

    export const signUp = asyncHandler(async (req: Request, res: Response): Promisevoid> => {
    const { firstname, lastname, email, password } = req.body;

    if (!firstname || !lastname || !email || !password) {
    res.status(400).json({ message: 'All fields are required' });
    }

    // Check if user already exists
    const userExists = await pool.query('SELECT * FROM users WHERE email = $1', [email]);

    if (userExists.rows.length > 0) {
    res.status(409).json({
    success: false,
    message: 'User already exists',
    });
    return;
    }

    // Upload avatar
    const avatarPath = req.file?.path;

    let avatar = null;
    if (avatarPath) {
    avatar = await uploadOnCloudinary(avatarPath);
    }

    // Make sure you bcrypt the password before saving in Database
    const newUser = await pool.query(
    'INSERT INTO users (firstname, lastname, email, password, avatar) VALUES ($1, $2, $3, $4, $5) RETURNING id, email, firstname, lastname, avatar',
    [firstname, lastname, email, password, avatar]
    );

    res.status(201).json({
    success: true,
    message: 'User signed up successfully',
    user: newUser.rows[0],
    });
    });










    8. Testing the Application

    Start the server:






    npm run dev







    Use tools like Postman or curl to test the /auth/signup endpoint by sending a POST request with a file and user data.





    Resources

    You can find the complete code for this project on GitHub:

    GitHub Repository: Image Upload with Node.js

    Feel free to explore the repository, clone it, and try it out for your projects!





    Conclusion

    By following this guide, you've implemented a robust image upload system using Node.js, Multer, and Cloudinary. This approach ensures reliability, scalability, and ease of maintenance. Dive deeper, customize the functionality, and share your thoughts. Together, we're building better backends!





    Enjoyed the read? If you found this article insightful or helpful, consider supporting my work by buying me a coffee. Your contribution helps fuel more content like this. Click here to treat me to a virtual coffee. Cheers!




    More...
Working...