One of the essential factors in running a hotel is having a well-trained team. Especially today, where electronic booking systems are the norm, making it possible to change room rates and availability while keeping customers updated instantly.
This tutorial will teach you how to build a complete hotel management system with Next.js and Strapi. The app will include customer authentication, image upload, hotel availability, hotel selection, and room selection.
What is Strapi?
Strapi is an open-source Node.js headless CMS for developing APIs and cross-platform applications ranging from simple single code bases (plugins) to complex multi-language big apps with great scalability.
Strapi is a cutting-edge and adaptable web development platform. It aims to provide an easy-to-use JavaScript library and a powerful platform to handle massive enterprise projects. Strapi is also free with Enterprise Edition plans to take the customization of your application to the next level.
What is Next?
Next.js is a React framework that lets you leverage the React framework to create supercharged, SEO-friendly, and incredibly user-friendly static web pages and web applications. Next.js is well-known for providing the greatest developer experience in creating production-ready applications with all the required functionality.
It has hybrid static and server rendering, TypeScript support, smart bundling, route prefetching, and other features that require no further configuration. You can learn more about Next.js from its official documentation.
What is Stripe?
Stripe is now one of the largest (if not the largest) payment processors. Stripe has established tools and SDKs that allow developers to construct and integrate secure, compliant payment processing into any app. Stripe claims to have the world's most powerful and user-friendly APIs.
Prerequisites
To follow along with this tutorial, you must install the following software and tools:
- Nodejs: Strapi works with Nodejs v14.x or 16.x.
- Nodejs\npm: NPM is included with Nodejs.
- yarn: Yarn is installable using npm:
- Code editor of your choice, I’ll make use of VS Code editor. ## Setting up the Strapi Back-end
To install Strapi, create an empty folder and run the Strapi installation command. We will split our project into two parts: the front-end (Next.js) and the back-end (Strapi).
Note: As of the writing of this article, I am using the most recent versions of every package. The future compatibility of these versions will be ensured by locking them. If you use the most recent versions of these packages, you might need to update the code in accordance with the relevant package's instructions. Upgrade software at your discretion.
Strapi Installation
To get started, create a new folder where to install Strapi. Now open your terminal and enter the commands below to install a new Strapi project.
mkdir strapi-Hotel-blog
cd strapi-Hotel-blog
npx create-strapi-app strapi-hotel-blog --quickstart
The preceding command will generate a new Strapi project and install the required dependencies.
Once the installation is completed, we must run the new Strapi project in development mode using the below command.
npm run develop
After running the above command, the Strapi server should start running at localhost:1337. On your preferred browser, go to localhost:1337/admin to set up our admin user account.
Then select the "Let's Start" button. When you select this button, your account will be created, and the Strapi UI for admin will appear:
Now that we have successfully installed Strapi on your computer, you will learn how to create a collection type for the hotel details in the next section.
Creating Collection Types
We will first create the collection types for our application. Each type of collection will hold information on the rooms that are available as well as customer details.
Let's begin by constructing the first collection type, which we'll call room. To build this collection type, follow the instructions below.
- From your dashboard menu, click on Content-Type Builder
- Click on Create New Collection Type
- Enter the display name for the collection type (room),
- Then click on "Continue."
Next, we’ll add the following fields to the room collection:
- CustomerName: (Text - Long Text)
- RoomID: (Text - Short Text)
- CustomerEmail: (Text - Short Text)
- CustomerPhone: (Text - Short Text)
- RoomPrice: (Number)
- (Short Text) PaymentStatus:
Next, we’ll create another collection-type named "check-in." This collection type will contain information about the customers and the room they booked. To create this collection type, follow the steps below.
- From your dashboard menu, click on Content-Type Builder
- Click on Create New Collection Type
- Enter the display name for the collection type (check-in)
- Then click on "Continue."
Next, we’ll add the following fields to the check-in collection.
- customerName: (Text - Long Text)
- RoomID: (Text - Short Text)
- BookDate: (Number)
- AmountPaid: (Text - Short Text)
- Status: (Short- text)
Great work! We have successfully created our content types. We’ll then move on to the next step: adding some hotels to our database. To do so, navigate from Content Manager to Room from the dashboard menu, and click "Create New Entry."
Let's create a hotel:
- Click on Add New Room.
- Enter the room details.
- Save it.
- Publish it.
Create as many rooms as you would like to see in your apps.
If we try to access the room collection type via an API call, it will give a permission error. To solve this, navigate to Settings>Roles and Public, click on Room, check all the options, and save it.
The collection types we needed for our hotel have been successfully constructed, and we have added as many rooms as possible.
Now we have successfully created our hotel backend using Strapi. In the next section, we will start creating our hotel front-end.
Setting Up The Strapi Back-end
We will require an empty directory to install the library and host our project root to host Next.js. You can read more about integrating Next.js with Strapi here.
Our project will be divided into the front-end (Next.js code) and the back-end (Strapi code).
Install the most recent versions of Next 13.1.0, React 18.2.0, and react-dom 18.2.0, and create project folders.
Developing the Back-end
To get started with next.js, we need to first install next.js on our computer. to do that open your terminal and run the command below
npx create-next-app@latest
What is Axios?
Axios is a node.js and browser-based promise-based HTTP client. Thanks to the same codebase, it can run in both the browser and Node.js. It uses the native Node.js HTTP module on the server and XMLHttpRequests on the client (browser). For additional information, you may read more on their website.
Axios installation
To install Axios, run the command below on your terminal.
Using npm
npm install axios
Building the Homepage
On the homepage, the user can browse through the list of all the available rooms in the hotel. From the project root directory, navigate to the pages folder and add the following code to index.js
import axios from "axios";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import { render } from "react-dom";
const Home = ({ rooms, error }) => {
const router = useRouter();
return (
<>
<div className="contner">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<main>
<div className="menu2"></div>
<div>
<center>
<h3>Available Rooms</h3>
</center>
<br />
</div>
<div className="rooms">
{rooms.data.map((restaurant) => (
<div className="room" key={restaurant.id}>
<img
className="room_img"
src="https://raw.githubusercontent.com/popoolatopzy/hotel/main/1.jpeg"
/>
<span className="price">{restaurant.attributes.roomName}</span>
<span className="price2">
${restaurant.attributes.roomPrice}
</span>
<br />
<br />
<center>
<span>{restaurant.attributes.roomDescription}</span> <br />
<button className="btn-default">
<a href={restaurant.attributes.Link}>Book Now </a>
</button>
</center>
</div>
))}
</div>{" "}
<br />
<br />
<br />
</main>
<style jsx global>{`
.menu {
position: fixed;
width: 100%;
height: 70px;
background-color: #084298;
}
.menu2 {
width: 100%;
height: 70px;
}
.title1 {
font-size: 25px;
color: white;
margin-top: 20px;
margin-left: 60px;
}
.title2 {
font-size: 25px;
color: white;
// margin-top: 20px;
margin-right: 60px;
float: right;
}
.price {
font-size: 22px;
font-weight: lighter;
margin-left: 10px;
}
.price2 {
float: right;
margin-right: 20px;
color: #ffb300;
font-size: 24px;
font-weight: lighter;
}
.static {
width: 100%;
height: 300px;
background-color: blue;
background-image: url("1.jpeg");
background-size: 100% 300px;
}
.rooms {
width: 100%;
height: 360px;
/*background-color:blue;*/
}
.room {
float: left;
margin-left: 90px;
width: 23%;
height: 360px;
/*background-color:red;*/
border-radius: 10px;
border: 7px solid #f8f9fc;
}
.room_img {
width: 100%;
max-height: 170px;
border-top-right-radius: 10px;
border-top-left-radius: 10px;
}
`}</style>
</div>
</>
);
};
Home.getInitialProps = async (ctx) => {
try {
const res = await axios.get("http://localhost:1337/api/rooms");
const rooms = res.data;
return { rooms };
} catch (error) {
return { error };
}
};
export default Home;
When the above code is run, we can navigate through the homepage and see the list of all the available rooms in the hotel.
Now let's create another page where users can get more information about the room and be able to make payments. To do that, create a new folder called view and index.js file in the pages folder.
Next, inside the index.js file, add the following code:
// view/index.js
import Head from "next/head";
import axios from "axios";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const Home = () => {
const [room_name, setroomname] = useState();
const [room_price, setroomprice] = useState();
const [room_description, setroomdescription] = useState();
const [email, setEmail] = useState("");
const [bookdate, setBookdate] = useState("");
const [checkout_date, setCheckout_date] = useState("");
const [sub, setsub] = useState("");
const { query } = useRouter();
const router = useRouter();
const getsurvey = async (value) => {
let Tasks = await axios.get("http://localhost:1337/api/rooms/" + value);
setroomname(Tasks.data.data.attributes.roomName);
setroomprice(Tasks.data.data.attributes.roomPrice);
setroomdescription(Tasks.data.data.attributes.roomDescription);
};
getsurvey(query.id);
function subbmit() {
// alert(fullname);
console.log(room_price);
if (
room_name != "" &&
room_price != "" &&
room_description != "" &&
email != "" &&
bookdate != "" &&
checkout_date != ""
) {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: {
RoomName: room_name,
RoomID: query.id,
BookDate: bookdate,
AmountPaid: room_price,
CheckoutDate: checkout_date,
Email: email,
},
}),
};
fetch("http://localhost:1337/api/check-ins", requestOptions).then(
(response) => response.json()
);
alert("Registration Successful...");
router.push("/view/dashboard");
}
}
useEffect(() => {
subbmit();
}, [sub]);
return (
<div className="contner">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<main>
<div className="menu">
<a href="./">
<span className="title1">MyHotelBook</span>
</a>
<a href="view/login">
<span className="title2">Login</span>
</a>
<a href="view/register">
<span className="title2">Register</span>
</a>
</div>
<div className="menu2"></div>
<br />
<br />
<center>
<h2>Check Out</h2>
</center>
<div className="preview">
<br />
<div className="show">
<img
className="img"
src="https://raw.githubusercontent.com/popoolatopzy/hotel/main/1.jpeg"
/>
<br />
<center>
<h2>{room_name}</h2>
</center>
<br />
<span>{room_description}</span>
</div>
<div className="checkout">
<h3>Book Now</h3>
<form className="wid">
<div className="form-group">
<label for="usr">
Price Per Night: <strong> ${room_price}</strong>
</label>
</div>
<br />
<div className="form-group">
<label for="usr">Check In Day:</label>
<input
type="date"
className="form-control"
id="usr"
defaultValue=""
onChange={(event) => setBookdate(event.target.value)}
/>
</div>{" "}
<br />
<div className="form-group">
<label for="usr">Check Out Day:</label>
<input
type="date"
className="form-control"
defaultValue=""
id="usr"
onChange={(event) => setCheckout_date(event.target.value)}
/>
</div>{" "}
<br />
<div className="form-group">
<label for="usr">Enter Email:</label>
<input
type="email"
defaultValue=""
className="form-control"
placeholder="Enter your registered email"
id="usr"
onChange={(event) => setEmail(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<input
type="button"
className="form-control btn-info"
value="Pay now"
onClick={(event) => setsub("enter")}
/>
</div>
<br />
</form>
</div>
</div>
</main>
<style jsx global>{`
.menu {
position: fixed;
width: 100%;
height: 70px;
background-color: #084298;
}
.menu2 {
width: 100%;
height: 70px;
}
.title1 {
font-size: 25px;
color: white;
margin-top: 20px;
margin-left: 60px;
}
.title2 {
font-size: 25px;
color: white;
// margin-top: 20px;
margin-right: 60px;
float: right;
}
.preview {
height: 600px;
width: 100%;
}
.show {
margin-left: 20px;
max-width: 420px;
height: 600px;
margin-top: 10px;
float: left;
}
.img {
margin-left: 20px;
max-height: 270px;
border-radius: 10px;
}
.hide {
height: 300px;
width: 100%;
}
.checkout {
height: 300px;
width: 40%;
float: right;
margin-right: 20px;
}
.wid {
width: 300px;
}
`}</style>
</div>
);
};
export default Home;
Restart the development server, and when you refresh the website, the relevant rooms should appear as intended.
Clicking on one of the rooms opens up another page with a clearer and larger image of the room you are about to book. It also opens up the booking page, where you can book as many rooms as you like at the cost of one per night.
Fantastic work. You successfully created the hotel's back-end so guests can view the rooms and make the appropriate reservations.
You will discover how to authenticate users in your app in the following section.
Admin Dashboard
Now you will build the dashboard for sign-up and login, enabling access to the features for both new and frequent users.
Authentication
During authentication calls, we'll send a POST request to the relevant register and login APIs to log in to currently logged-in users and register new users. While the user object shows the user's username in the header bar, the JWT token Strapi returns can validate server-side transactions.
You can read more on Strapi documentation on Authentication here
Compared to a normal client-side authentication solution, authenticating using Next requires more consideration because we need to know whether the code is being processed on the client or the server. Given the distinctions between server and client architectures, client-only code shouldn't be permitted to operate on a server.
We'll use the built-in JWT authentication in Strapi for authentication. This will make it simple for us to add features, manage features, log in to the application, and check the status of users within our application.
To see, amend, activate, and delete users as necessary, your back-end admin panel will have a user administration GUI.
Let's create a collection-type named "customer." This collection type will contain the customer's login details. To create this collection type, follow the steps below.
- From your dashboard menu, click on
**Content-Type Builder**
- Click on
**Create New Collection Type**
- Enter the display name for the collection type (
**customer**
), - Then click on "
**Continue**
."
Next, we’ll add the following fields to the customer collection.
- fullname: (Text - Short Text)
- email: (Email)
- phone_no: (Text - Short Text)
- password: (Text - Short Text)
Let's create another collection-type named "customer." This collection type will contain the customer's login details. To create this collection type, follow the steps below.
- From your dashboard menu, click on Content-Type Builder
- Click on Create New Collection Type
- Enter the display name for the collection type (customer),
- Then click on "Continue."
Next, we’ll add the following fields to the customer collection.
- RoomName: (Text - Short Text)
- RootID: (Text)
- BookDate: (Text - Short Text)
- Status: (Text - Short Text)
- CheckoutDate: (Text - Short Text)
- Email: (Text - Short Text)
- AmountPaid: (Number - Integer)
Register A User
We need to add an authentication method to our hotel system so that users can log in to their dashboard to view details about the rooms that they booked. We have created the necessary collection type to store the user details. To implement the login functionality, create a new file called register.js inside pages/view folder.
Inside the ´register.js´ file, paste the following code.
import Head from "next/head";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const Home = () => {
const router = useRouter();
const [fullname, setFullname] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [password, setPassword] = useState("");
const [sub, setsub] = useState("");
const { query } = useRouter();
function subbmit() {
// alert(fullname);
if (fullname != "" && email != "" && phone != "" && password != "") {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: {
fullname: fullname,
email: email,
phone_no: phone,
password: password,
},
}),
};
fetch("http://localhost:1337/api/customers", requestOptions).then(
(response) => response.json()
);
alert("Registration Successful...");
router.push("/view/login");
}
}
useEffect(() => {
subbmit();
}, [sub]);
return (
<div className="contner">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<main>
<div className="checkout">
<form className="wid">
<div className="form-group">
<center>
{" "}
<h4> Register</h4>
</center>
</div>
<br />
<div className="form-group">
<label for="usr">Enter fullname:</label>
<input
type="text"
className="form-control"
placeholder="Enter fullname"
onChange={(event) => setFullname(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<label for="usr">Email address:</label>
<input
type="email"
className="form-control"
placeholder="Enter email address"
onChange={(event) => setEmail(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<label for="usr">Phone number:</label>
<input
type="number"
className="form-control"
placeholder="Enter phone number"
onChange={(event) => setPhone(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<label for="usr">Password:</label>
<input
type="password"
className="form-control"
placeholder="Enter password"
onChange={(event) => setPassword(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<input
type="button"
className="form-control btn-info"
value="Sign Up"
onClick={(event) => setsub("enter")}
/>
</div>
<br />
</form>
</div>
</main>
<style jsx global>{`
.checkout {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.wid {
width: 300px;
}
`}</style>
</div>
);
};
export default Home;
Save it and navigate to http://localhost:3000/view/register
, you should have the following output.
Login
Since we have the registration page, we also need the login page to allow users to log into their dashboards. To add the login features, create a new file called login.js inside the pages/view folder and add the following code inside ´login.js´ file.
import Head from "next/head";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import axios from "axios";
const Home = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [sub, setsub] = useState("");
const router = useRouter();
function subbmit() {
if (email != "" && password != "") {
const requestOptions = {
method: "GET",
headers: { "Content-Type": "application/json" },
};
fetch("http://localhost:1337/api/customers", requestOptions).then(
(response) =>
response.json().then((data) => {
var test = data.data;
// console.log(data.data);
test.forEach((element) => {
if (
element.attributes.email == email &&
element.attributes.password == password
) {
console.log("hh");
localStorage.setItem("userID", element.id);
router.push("/view/dashboard");
}
});
})
);
}
}
useEffect(() => {
subbmit();
}, [sub]);
// function
return (
<div className="contner">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<main>
<div className="checkout">
<form className="wid">
<div className="form-group">
<center>
{" "}
<h4> Login</h4>
</center>
</div>
<br />
<div className="form-group">
<label for="usr">Email address:</label>
<input
type="email"
className="form-control"
placeholder="Enter email address"
onChange={(event) => setEmail(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<label for="usr">Password:</label>
<input
type="password"
className="form-control"
placeholder="Enter password"
onChange={(event) => setPassword(event.target.value)}
/>
</div>
<br />
<div className="form-group">
<input
type="button"
className="form-control btn-info"
value="Login"
onClick={(event) => subbmit()}
/>
</div>
<br />
</form>
</div>
</main>
<style jsx global>{`
.checkout {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.wid {
width: 300px;
}
`}</style>
</div>
);
};
export default Home;
Save it and navigate to http://localhost:3000/view/login. You should have the following output.
The authentication process was completed in this section, and we also created a user interface for clients to create accounts with the hotel and log in as needed. We also connected it to the Strapi back-end.
Reservation
Now we will login as users after going through the registration process
Dashboard
On the dashboard page, a registered and logged user can view the list of the rooms that they have paid for. To archive this, create another file called dashboard.js inside pages/view and paste the following code into it:
import Head from "next/head";
import axios from "axios";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const Home = ({ rooms, error }) => {
const router = useRouter();
*// const [rooms, setRooms] = useState();*
function checkelogin() {
console.log(localStorage.getItem("userID"));
if (localStorage.getItem("userID") == "null") {
router.push("/view/login");
}
}
useEffect(() => {
checkelogin();
}, []);
return (
<div className="contner">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<main>
<div className="menu">
<a href="/">
<span className="title1">MyHotel</span>
</a>
<a href="/view/dashboard">
<span className="title2">Account</span>
</a>
</div>
<div className="menu2"></div>
<br />
<br />
<br />
<br />
<div className="preview">
<br />
<div className="show">
<img
className="img"
src="https://raw.githubusercontent.com/popoolatopzy/hotel/main/1.jpeg"
/>
<br />
<center>
<h2>Jerry Oluseye</h2>
{/* <br /> */}
<span> Oluseye@gmail.com</span>
</center>
</div>
<div className="checkout">
<h3>Checked Rooms</h3>
<table className="table table-striped">
<thead>
<tr>
{/* <th>Ticket ID</th> */}
<th>Room Name</th>
<th>Chect Out Date</th>
<th>Price</th>
{/* <th>Email</th> */}
</tr>
</thead>
<tbody>
{rooms.data.map((checkin) => (
<tr>
<td>{checkin.attributes.RoomName}</td>
<td>{checkin.attributes.CheckoutDate}</td>
<td>${checkin.attributes.AmountPaid}</td>
</tr>
))}
</tbody>
</table>
<br />
</div>
</div>
</main>
<style jsx global>{`
.menu {
position: fixed;
width: 100%;
height: 70px;
background-color: #084298;
}
.menu2 {
width: 100%;
height: 70px;
}
.title1 {
font-size: 25px;
color: white;
margin-top: 20px;
margin-left: 60px;
}
.title2 {
font-size: 25px;
color: white;
// margin-top: 20px;
margin-right: 60px;
float: right;
}
.preview {
height: 600px;
width: 100%;
}
.show {
margin-left: 20px;
width: 560px;
height: 600px;
margin-top: 10px;
float: left;
// margin-right: 20px;
// background-color: red;
}
.img {
margin-left: 200px;
height: 100px;
width: 100px;
border-radius: 100%;
}
.hide {
height: 300px;
width: 100%;
}
.checkout {
height: 300px;
width: 50%;
float: right;
// background-color: red;
margin-right: 60px;
}
.wid {
width: 300px;
}
`}</style>
</div>
);
};
Home.getInitialProps = async (ctx) => {
try {
const res = await axios.get("http://localhost:1337/api/check-ins");
const rooms = res.data;
return { rooms };
} catch (error) {
return { error };
}
};
export default Home;
Suppose you register and log in to your dashboard. The dashboard should be like the image below:
Conclusion
Congratulations on successfully developing a hotel management system with Strapi and Next.js. If you have any queries, please leave them in the comments section or simply say hello.
You can check out the code on GitHub here.
If you would like to learn more about how to customize a Strapi application and deploy it, check out these resources:
- How To Deploy Your First Strapi Project To Strapi Cloud
- Strapi Internals: Customizing the Backend
- How to Deploy and Scale Strapi on a Kubernetes Cluster
Continue discussing this topic further or connect with more people using Strapi on our Discord community. It is a great place to share your thoughts, ask questions, and participate in live discussions.