Building a Node.js Server-Side Application with Sequelize and Express: Beginner's Guide
Learn to Build a Beginner-Friendly Node.js Server-Side Application with Sequelize and Express
Hey guys!
In the previous article, we discussed the process of establishing a connection between Node.js and MySQL database using Sequelize. In this subsequent article, we'll focus on developing the necessary models, controllers, and routes to complete the server-side application for the TypeScript sign-up form, which we introduced earlier. Without further ado, let's delve into the implementation details.
In our route folder let's create three additional folders: models, controllers and routes. - The models folder will contain code that implements the creation of entities, their structures and the relationship between them.
- The controller folder will typically handle all the requests and responses in our application.
- The routes folder will be responsible for defining the API endpoints of our application that the client interacts with.
Inside the models folder, create a file called User.js
and import DataTypes from sequelize.
import { DataTypes } from "sequelize";
Now let's create an arrow function called User
that takes two arguments (sequelize
and Sequelize
). I'll explain why we're adding these two parameters later on.
const User = (sequelize, Sequelize) => {
}
Inside the function, let's define a sequelize model called User for a table in our database.
const User = sequelize.define("users", {
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
});
The type
property specifies that the column can only hold strings while the allowNull
property is set to false requiring the column to not have null values.
Let's return the User model so that we can access it whenever the User function is called. Here's how the file looks:
import { DataTypes } from "sequelize";
const User = (sequelize, Sequelize) => {
const User = sequelize.define("users", {
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
});
return User;
}
export default User;
In the models folder, let's add another file named index.js
. This file will be responsible for managing the relationships between entities in our database. To illustrate this, let's consider an online platform similar to Coursera, Udemy, or Skillshare. These platforms have entities such as students and courses, and these entities need to have relationships defined between them.
For example, a student can be enrolled in multiple courses, and a course can have one or many students enrolled in it. Additionally, there can be scenarios where multiple students are enrolled in multiple courses. It's important to establish these relationships between the entities to reflect the real-world associations accurately.
By creating an index.js
file in the models folder, we can define and configure these relationships using Sequelize's associations. This allows us to efficiently navigate and query the database based on these relationships, enabling us to perform operations such as fetching a student's enrolled courses or retrieving all students enrolled in a particular course.
Even though in our Application we only have a single entity, our application will have a clear and organized way of handling entity relationships, promoting better database management and seamless interaction between related data entities.
In index.js,
Import Sequelize from sequelize
and sequelize
from db.js.
(read the previous article if you haven't to check out how we established the connection to the database in detail)
import Sequelize from "sequelize";
import sequelize from '../config/db.js';
create an empty object called db and create instances of Sequelize
and sequelize
:
const db = {};
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.Sequelize = Sequelize;
: This line assigns the Sequelize object to theSequelize
property of thedb
object. This allows access to the Sequelize library and its functionalities through thedb
object.db.sequelize = sequelize;
: This line assigns thesequelize
instance to thesequelize
property of thedb
object. Thesequelize
instance represents a connection to the database and provides methods for executing queries and interacting with the database.
Next, import the User model with the following code:
import User from "./User.js";
db.User = User(sequelize, Sequelize)
This is how the file looks:
import Sequelize from "sequelize";
import sequelize from '../config/db.js'
const db = {}
db.sequelize = sequelize;
db.Sequelize = Sequelize;
import User from "./User.js";
db.User = User(sequelize, Sequelize);
export default db;
If you recall well, The User
we just imported has two parameters: sequelize and Sequelize. In the above code, we've created an instance of the db (db.User
) and assigned the imported User
function to it. We then passed the sequelize
and Sequelize
as arguments to this function.
At this point, this code may appear to be repetitive and somewhat absurd because we don't have relationships or more specifically, any other entity in our database. But it's important to note that, we have made our work easier and written sufficient code by creating a centralized object (db
) for Sequelize references, including the Sequelize instance (db.sequelize
), the Sequelize library (db.Sequelize
), and the User
model (db.User
). The key point to take here, is that we've built an object that provides a convenient way to access the sequelize
,Sequelize
and User
objects throughout our entire application.
On to the next step.
Now let's handle the requests
and responses
by adding a file in the controllers folder called UserController.js
. Inside the file, import the db
object and assign the User
model to a variable named User
.
import db from "../models/index.js";
const User = db.User;
Now let's create a function that will handle that receive the data sent from the client side and store it in the database.
import db from "../models/index.js";
const User = db.User;
export const createUser = async(req, res) => {
const user = req.body;
try {
const response = await User.create(user);
if(!response) {
res.status(500).json({message: "INTERNALE SERVER ERROR"})
}
res.status(201).json({message: "User Created"})
} catch (error) {
res.status(400).json(error)
}
}
export const createUser = async (req, res) => { ... }
: This line exports a function namedcreateUser
as the default export of this module. The function handles creating a new user in the database when invoked.const user = req.body;
: This line extracts the user object from the request's body. It assumes that the user information is sent in the request body, usually as JSON.try { ... } catch (error) { ... }
: This block of code wraps the asynchronous code and handles any potential errors that might occur during the execution.const response = await User.create(user);
: This line uses theUser
model'screate
method to create a new user in the database. It passes theuser
object extracted from the request body as the data for creating the user. Theawait
keyword is used to wait for thecreate
operation to complete before proceeding.if (!response) { ... }
: This condition checks if the response from theUser.create
operation is falsy. If it is, it means that the user creation was not successful. In that case, it sends a response with a 500 status code and a JSON object containing an error message.res.status(201).json({message: "User Created"})
: If the user creation is successful, this line sends a response with a 201 status code (indicating successful creation) and a JSON object containing a success message.res.status(400).json(error)
: If an error occurs during the execution of thetry
block, this line sends a response with a 400 status code (indicating a bad request) and a JSON object containing the error details.
In the next step, add a file called UserRoutes.js
inside the routes folder.
Import express
from express and UserController.js
function that we just created.
import express from 'express'
import { createUser } from '../controllers/UserController.js'
Create an instance of express Router ()
called router.
export const router = express.Router();
Now let's create the endpoint:
Here's an explanation of the code:
router.post('/users', createUser);
router.post('/users', createUser);
router.post
('/users', createUser);
: This line sets up a route using the Express router. It specifies that for the HTTP POST method and the '/users' endpoint, thecreateUser
function should be called to handle the request.router
: This variable represents an instance of the Express router, which is a middleware that allows you to define routes and handle HTTP requests.post
: This method is used to define a route that handles HTTP POST requests.'/users'
: This string represents the endpoint or URL path for the route. In this case, it is set to '/users'.createUser
: This is the handler function that will be executed when an HTTP POST request is made to the '/users' endpoint. It is passed as the second argument to therouter.post
()
method.Here's how the file looks:
import express from 'express' import { createUser } from '../controllers/UserController.js'; export const router = express.Router(); router.post('/users', createUser);
Finally in our root file (index.js
) we'll import the router
from the routes folder.
Here's how our index.js
file looks:
import express from 'express'
import sequelize from './config/db.js'
const app = express()
sequelize
.sync({ alter: true })
.then(() => {
console.log("Tables created.");
})
.catch((err) => {
console.log(err);
});
const port = 3300
app.listen(port, () => {
console.log(`SERVER RUNNING ON PORT ${port}`);
})
( we created this file in the previous article. )
Import router
in this file and add the following:
import express from 'express'
import sequelize from './config/db.js'
import { router } from './routes/UserRoutes.js'; //Added code
const app = express()
app.use(express.json()) //Added code
app.use(router) //Added code
sequelize
.sync({ alter: true })
.then(() => {
console.log("Tables created.");
})
.catch((err) => {
console.log(err);
});
const port = 3300
app.listen(port, () => {
console.log(`SERVER RUNNING ON PORT ${port}`);
})
app.use(express.json())
: This middleware is typically used to handle JSON data in API endpoints.app.use(router)
: This line applies therouter
middleware to the Express application (app
).
To run the server execute npm start
in the terminal. This is the output:
Now if we run our TypeScript sign-up form that we developed earlier and fill out the form, the data should be sent to our node.js application and eventually stored in the MySQL database.
However, if we submit the form we get these two errors:
Access to XMLHttpRequest at 'http://localhost:3300/apiv1/signup' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
AxiosError {message: 'Network Error', name: 'AxiosError', code: 'ERR_NETWORK', ...
To fix these errors, we need to:
Install
cors
in our server and add our Typescript live site's address to it.change the endpoint in our Typescript form and make sure it matches our server's endpoint.
First, let's fix the Modify the Typescript form and replace the current endpoint with this:
const handleSubmit = async (e: React.ChangeEvent<HTMLFormElement>) => { e.preventDefault(); try { const response = await axios.post('http://localhost:3300/users', formData);//replaced the endpoint to point to our server's endpoint console.log(response); }catch (error) { console.error(error); } }
Secondly, in our server-side application install cors:
npm install cors
In the root folder (index.js
), import cors
from cors and add :
app.use(cors({ origin: "http://localhost:3000" }));
Here's how the file looks:
import express from "express";
import sequelize from "./config/db.js";
import { router } from "./routes/UserRoutes.js";
import cors from "cors";
const app = express();
app.use(express.json());
app.use(cors({ origin: "http://localhost:3000" })); // Set the allowed origin to the frontend URL
app.use(router);
sequelize
.sync({ alter: true })
.then(() => {
console.log("Tables created.");
})
.catch((err) => {
console.log(err);
});
const port = 3300;
app.listen(port, () => {
console.log(`SERVER RUNNING ON PORT ${port}`);
});
Now if we fill out the form correctly and submit it, this is the response we see in the browser's console:
In the data
object, we get the message "User Created" confirming that the User has signed up.
That's it. We have now successfully signed up a user in React with TypeScript and stored their data in MySQL database using Node.js.
In this article, we explored the process of building a server-side application with Node.js using Sequelize and Express. We started by establishing a connection between Node.js and MySQL database using Sequelize. Then, we delved into creating models, controllers, and routes to handle the server-side functionalities, specifically focusing on implementing a sign-up form for a TypeScript application.
We organized our code into separate folders, including the models, controllers, and routes. The models folder contained the entity structure and relationships, while the controllers handled the requests and responses. The routes folder defined the API endpoints for the client interaction.
We saw how to define a Sequelize model for the User entity and configure relationships between entities using associations. By leveraging Sequelize's powerful features, we ensured a well-structured and efficient database management system.
Furthermore, we learned about the importance of the db object, which provided a centralized reference to the Sequelize and sequelize instances, as well as the User model. This approach facilitated seamless access to these objects throughout our application.
We also implemented a createUser function in the UserController, which handled the creation of a new user in the database. We utilized async/await and error handling techniques to ensure a smooth and robust user creation process.
Finally, we configured our Express application by using the express.json() middleware to handle JSON data and integrating the router middleware to recognize and process the defined routes.
By following this step-by-step guide, you now have a solid foundation for developing server-side applications with Node.js, Sequelize, and Express. The knowledge and techniques presented here will empower you to build complex and scalable applications with ease.
Here are the links to the GitHub Repositories:
- Front-end
- Back-end
Happy coding!
Follow me on social media: