DEV Community

Cover image for Building a Scalable Node.Js Backend: Sequelize Models, CRUD Operations & Clean Architecture
M Ahsan Hameed
M Ahsan Hameed

Posted on

Building a Scalable Node.Js Backend: Sequelize Models, CRUD Operations & Clean Architecture

How to Set Up a Node.js Express App with Sequelize and AWS MySQL RDS + Source Code

Two Ways to Get Started:

1️⃣ Follow the step-by-step guide in the tutorial above.
2️⃣ Download the source code and practice at your own pace.

🚀 An exciting journey awaits—let’s begin!

Introduction

In this post, we will:

  1. Define Sequelize models for User and Post tables.
  2. Set up the routes, controllers, and services to handle CRUD operations.
  3. Maintain a clean and scalable code structure by separating concerns into different folders.

Let’s dive in and organize our code to make it more maintainable!

Step 1: Define Sequelize Models (Tables)

Sequelize models represent the structure of the database tables. Let’s start by defining the models for User and Post.

1.1. Create the User Model

In the models folder, create a file named User.js:

// src/models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');

const User = sequelize.define('User', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
  },
});

module.exports = User;
Enter fullscreen mode Exit fullscreen mode

This model defines the User table with id, username, and email fields.

1.2. Create the Post Model

Next, create a Post.js file in the models folder:

// src/models/Post.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const User = require('./User');  // Reference to User model

const Post = sequelize.define('Post', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  },
  title: {
    type: DataTypes.STRING,
    allowNull: false,
  },
  content: {
    type: DataTypes.TEXT,
    allowNull: false,
  },
});

// Define relationship: One User has many Posts
Post.belongsTo(User, { foreignKey: 'userId' });

module.exports = Post;
Enter fullscreen mode Exit fullscreen mode

This defines the Post model with title, content, and a relationship to the User model.

Step 2: Create Routes, Controllers, and Services

To maintain clean and scalable code, we will separate the application logic into routes, controllers, and services.

2.1. Folder Structure

Let’s organize the folders as follows:

/src
  /controllers
    userController.js
    postController.js
  /routes
    userRoutes.js
    postRoutes.js
  /services
    userService.js
    postService.js
  /models
    User.js
    Post.js
  app.js
Enter fullscreen mode Exit fullscreen mode

2.2. Create Services for Business Logic

The service layer will handle the actual business logic, such as interacting with the database.

userService.js
Create the services/userService.js file:

// services/userService.js
const User = require('../models/User');

const createUser = async (username, email) => {
  const user = await User.create({ username, email });
  return user;
};

const getAllUsers = async () => {
  const users = await User.findAll();
  return users;
};

const getUserById = async (id) => {
  const user = await User.findByPk(id);
  return user;
};

const updateUser = async (id, username, email) => {
  const user = await User.findByPk(id);
  if (user) {
    user.username = username;
    user.email = email;
    await user.save();
    return user;
  }
  return null;
};

const deleteUser = async (id) => {
  const user = await User.findByPk(id);
  if (user) {
    await user.destroy();
    return true;
  }
  return false;
};

module.exports = {
  createUser,
  getAllUsers,
  getUserById,
  updateUser,
  deleteUser,
};
postService.js
Create the services/postService.js file:

javascript
Copy
// services/postService.js
const Post = require('../models/Post');

const createPost = async (title, content, userId) => {
  const post = await Post.create({ title, content, userId });
  return post;
};

const getAllPosts = async () => {
  const posts = await Post.findAll();
  return posts;
};

const getPostById = async (id) => {
  const post = await Post.findByPk(id);
  return post;
};

const updatePost = async (id, title, content) => {
  const post = await Post.findByPk(id);
  if (post) {
    post.title = title;
    post.content = content;
    await post.save();
    return post;
  }
  return null;
};

const deletePost = async (id) => {
  const post = await Post.findByPk(id);
  if (post) {
    await post.destroy();
    return true;
  }
  return false;
};

module.exports = {
  createPost,
  getAllPosts,
  getPostById,
  updatePost,
  deletePost,
};
2.3. Create Controllers for Handling Requests
The controller layer will handle HTTP requests and interact with the services.

userController.js
Create the controllers/userController.js file:

javascript
Copy
// controllers/userController.js
const userService = require('../services/userService');

const createUser = async (req, res) => {
  const { username, email } = req.body;
  try {
    const user = await userService.createUser(username, email);
    res.status(201).json(user);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const getAllUsers = async (req, res) => {
  try {
    const users = await userService.getAllUsers();
    res.status(200).json(users);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const updateUser = async (req, res) => {
  const { id } = req.params;
  const { username, email } = req.body;
  try {
    const user = await userService.updateUser(id, username, email);
    if (user) {
      res.status(200).json(user);
    } else {
      res.status(404).json({ message: 'User not found' });
    }
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const deleteUser = async (req, res) => {
  const { id } = req.params;
  try {
    const success = await userService.deleteUser(id);
    if (success) {
      res.status(200).json({ message: 'User deleted' });
    } else {
      res.status(404).json({ message: 'User not found' });
    }
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

module.exports = {
  createUser,
  getAllUsers,
  updateUser,
  deleteUser,
};
Enter fullscreen mode Exit fullscreen mode

postController.js
Create the controllers/postController.js file:

// controllers/postController.js
const postService = require('../services/postService');

const createPost = async (req, res) => {
  const { title, content, userId } = req.body;
  try {
    const post = await postService.createPost(title, content, userId);
    res.status(201).json(post);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const getAllPosts = async (req, res) => {
  try {
    const posts = await postService.getAllPosts();
    res.status(200).json(posts);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const updatePost = async (req, res) => {
  const { id } = req.params;
  const { title, content } = req.body;
  try {
    const post = await postService.updatePost(id, title, content);
    if (post) {
      res.status(200).json(post);
    } else {
      res.status(404).json({ message: 'Post not found' });
    }
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const deletePost = async (req, res) => {
  const { id } = req.params;
  try {
    const success = await postService.deletePost(id);
    if (success) {
      res.status(200).json({ message: 'Post deleted' });
    } else {
      res.status(404).json({ message: 'Post not found' });
    }
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

module.exports = {
  createPost,
  getAllPosts,
  updatePost,
  deletePost,
};
Enter fullscreen mode Exit fullscreen mode

2.4. Create Routes for the API

Now, let's define the routes to connect the controllers to our app.

userRoutes.js
Create the routes/userRoutes.js file:

// routes/userRoutes.js
const express = require('express');
const userController = require('../controllers/userController');
const router = express.Router();

router.post('/users', userController.createUser);
router.get('/users', userController.getAllUsers);
router.put('/users/:id', userController.updateUser);
router.delete('/users/:id', userController.deleteUser);

module.exports = router;
postRoutes.js
Create the routes/postRoutes.js file:

javascript
Copy
// routes/postRoutes.js
const express = require('express');
const postController = require('../controllers/postController');
const router = express.Router();

router.post('/posts', postController.createPost);
router.get('/posts', postController.getAllPosts);
router.put('/posts/:id', postController.updatePost);
router.delete('/posts/:id', postController.deletePost);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up Express Application and Routes

Finally, let’s bring everything together in the app.js file:

// app.js
const express = require('express');
const sequelize = require('./db');
const userRoutes = require('./routes/userRoutes');
const postRoutes = require('./routes/postRoutes');

const app = express();
const port = 3000;

app.use(express.json());
app.use(userRoutes);
app.use(postRoutes);

sequelize.authenticate().then(() => {
  console.log('Database connected successfully!');
}).catch((error) => {
  console.error('Unable to connect to the database:', error);
});

sequelize.sync({ force: true }).then(() => {
  console.log('Database synced');
});

app.listen(port, () => {
  console.log(`App running at http://localhost:${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Test Your CRUD Endpoints

Now that we’ve defined the models and set up CRUD operations, let’s test everything using Postman or cURL:

  • POST /users: Create a new user.
  • GET /users: Get all users.
  • PUT /users/🆔 Update a user by id.
  • DELETE /users/🆔 Delete a user by id.
  • POST /posts: Create a new post.
  • GET /posts: Get all posts.

DOWNLOAD SOURCE CODE 'crud' BRANCH

Conclusion

In this post, we’ve:

  • Defined Sequelize models for User and Post.
  • Created routes, controllers, and services to handle CRUD operations.
  • Structured the code into separate folders for routes, controllers, and services to maintain a clean and scalable architecture.

In the next post, we’ll explore authentication, authorization, and deployment.

Top comments (0)