You are currently viewing Node.js Blog Post API: Admin Auth, Image Upload, Likes & Shares to Boost Social Media Engagement

Node.js Blog Post API: Admin Auth, Image Upload, Likes & Shares to Boost Social Media Engagement

In this article we’ll create full rest API for blog application with basic social media management functionality

In today’s digital landscape, creating a secure and efficient API is paramount for successful web applications. This article explores the process of building a robust Node.js blog API with essential features. We delve into admin authentication, image upload handling, likes, shares, and even integration with social media platforms. To ensure data integrity and user trust, we emphasize security measures like HTTPS implementation, rate limiting to prevent abuse, and utilization of security headers through the helmet middleware. By following these best practices, you’ll craft a high-performance blog API that not only engages users but also safeguards their information.

Discover the art of creating a dynamic Node.js blog API that excels in both functionality and security. Uncover the intricacies of implementing admin authentication, seamlessly handling image uploads, and incorporating the power of likes and shares to bolster social media interaction. Elevate your web experience by diving into the world of Node.js and crafting an API that not only captures attention but also ensures data integrity and user satisfaction.

required dependencies like express, mongoose, multer, and others need to be installed using npm before proceeding

Set up the project structure

- blog-api/
  |- models/
  |  |- user.js
  |  |- post.js
  |- routes/
  |  |- auth.js
  |  |- post.js
  |- controllers/
  |  |- authController.js
  |  |- postController.js
  |- middlewares/
  |  |- authMiddleware.js
  |- app.js

Create the user and post models in the models folder.

// models/user.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: { type: String, unique: true, required: true },
  password: { type: String, required: true },
  // Add more fields as per your requirements (e.g., name, email, etc.)
});

module.exports = mongoose.model('User', userSchema);

// models/post.js
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  images: [{ type: String }],
  likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  shares: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  // Add more fields as per your requirements (e.g., tags, categories, etc.)
});

module.exports = mongoose.model('Post', postSchema);

Set up the authentication routes and controllers in the routes and controllers folders.

// routes/auth.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/login', authController.login);

module.exports = router;

// controllers/authController.js
const bcrypt = require('bcrypt');
const User = require('../models/user');

exports.login = async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);

    if (!isPasswordValid) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }

    // Generate and return a JWT token for authenticated user
    // Implement JWT token generation and return it in the response
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Server error' });
  }
};

Implement the post-related routes and controllers in the routes and controllers folders.

// routes/post.js
const express = require('express');
const router = express.Router();
const postController = require('../controllers/postController');
const authMiddleware = require('../middlewares/authMiddleware');

router.post('/post', authMiddleware, postController.createPost);
router.get('/post/:postId', postController.getPost);
router.post('/post/:postId/like', authMiddleware, postController.likePost);
router.post('/post/:postId/share', authMiddleware, postController.sharePost);

module.exports = router;

// controllers/postController.js
const Post = require('../models/post');

exports.createPost = async (req, res) => {
  // Handle image upload using Multer and store image URLs in the post document
  const { title, content } = req.body;
  const images = req.files.map((file) => file.path);

  try {
    const post = await Post.create({ title, content, images });
    res.status(201).json({ message: 'Post created successfully', post });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Server error' });
  }
};

exports.getPost = async (req, res) => {
  const postId = req.params.postId;

  try {
    const post = await Post.findById(postId);
    res.status(200).json(post);
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Server error' });
  }
};

exports.likePost = async (req, res) => {
  const postId = req.params.postId;
  const userId = req.user._id; // Assuming the authenticated user object is available in req.user

  try {
    const post = await Post.findById(postId);

    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }

    if (!post.likes.includes(userId)) {
      post.likes.push(userId);
      await post.save();
    }

    res.status(200).json({ message: 'Post liked successfully' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Server error' });
  }
};

exports.sharePost = async (req, res) => {
  const postId = req.params.postId;
  const userId = req.user._id; // Assuming the authenticated user object is available in req.user

  try {
    const post = await Post.findById(postId);

    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }

    if (!post.shares.includes(userId)) {
      post.shares.push(userId);
      await post.save();
    }

    res.status(200).json({ message: 'Post shared successfully' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Server error' });
  }
};

Configure Express and connect to the database in app.js.

const express = require('express');
const mongoose = require('mongoose');
const multer = require('multer');
const app = express();

// Set up Multer storage for image uploads
const upload = multer({ dest: 'uploads/' });

// Middleware to parse request body
app.use(express.json());

// Connect to the MongoDB database using Mongoose
mongoose.connect('mongodb://localhost:27017/blogdb', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

// Set up routes
app.use('/auth', require('./routes/auth'));
app.use('/api', require('./routes/post'));

// Start the server
const port = 3000;
app.listen(port, () => {
  console.log(`Server started on http://localhost:${port}`);
});

Error Handling:

  • Use a standardized format for error responses to provide consistent information to clients.
  • Implement a global error handling middleware to catch and format errors.
// middlewares/errorMiddleware.js
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Something went wrong' });
};

module.exports = errorHandler;
// app.js
const errorMiddleware = require('./middlewares/errorMiddleware');

// ...

app.use(errorMiddleware);

Input Validation:

  • Use a validation library like joi or express-validator for validating request data.
  • Validate user inputs before processing them in the controller.
npm install joi
// controllers/postController.js
const Joi = require('joi');

// ...

exports.createPost = async (req, res) => {
  const schema = Joi.object({
    title: Joi.string().required(),
    content: Joi.string().required(),
  });

  const { error } = schema.validate(req.body);

  if (error) {
    return res.status(400).json({ message: error.details[0].message });
  }

  // ...
};

Security Measures:

  • Hash user passwords before storing them in the database using bcrypt.
  • Implement JWT-based authentication for admin access.
  • Ensure proper CORS configuration to restrict unauthorized access.
npm install bcrypt jsonwebtoken cors
// app.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const cors = require('cors');

app.use(cors()); // Use appropriate CORS options

// ...

exports.login = async (req, res) => {
  // ...

  const isPasswordValid = await bcrypt.compare(password, user.password);

  if (!isPasswordValid) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }

  const token = jwt.sign({ userId: user._id }, 'secretKey', { expiresIn: '1h' });
  res.json({ token });
};

File Upload Security:

  • Set file size limits and whitelist allowed file types using multer options.
const multer = require('multer');

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, file.fieldname + '-' + uniqueSuffix);
  },
});

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 1024 * 1024 * 5, // 5MB file size limit
  },
  fileFilter: (req, file, cb) => {
    if (['image/jpeg', 'image/png'].includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Invalid file type'));
    }
  },
});

Environment Variables:

  • Use environment variables to store sensitive information like database connection strings, API keys, etc.
  • Install and use the dotenv package to manage environment variables.
npm install dotenv
// app.js
require('dotenv').config(); // Load environment variables from .env file

// ...

mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

HTTPS:

  • Use HTTPS to encrypt data transmitted between the client and the server.
  • Generate or acquire SSL certificates (e.g., from Let’s Encrypt).
  • Redirect HTTP requests to HTTPS.
// app.js
const https = require('https');
const fs = require('fs');

const privateKey = fs.readFileSync('path/to/privatekey.pem', 'utf8');
const certificate = fs.readFileSync('path/to/certificate.pem', 'utf8');
const credentials = { key: privateKey, cert: certificate };

const httpsServer = https.createServer(credentials, app);

httpsServer.listen(443, () => {
  console.log('HTTPS Server started on port 443');
});

Rate Limiting:

  • Implement rate limiting to prevent abuse and ensure fair usage of your API.
  • Use the express-rate-limit middleware for rate limiting.
npm install express-rate-limit
// app.js
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
});

app.use('/api/', apiLimiter);

Security Best Practices:

  • Implement security headers to prevent common security vulnerabilities.
  • Use the helmet middleware to set security headers.
npm install helmet
// app.js
const helmet = require('helmet');

app.use(helmet());

Leave a Reply