In this tutorial, we will learn how to build a simple hotel booking system with Strapi and Nuxt.js. The hotel booking system will allow us to enter new guests into our database, send emails to our guests to notify them of their booking, and check out guests from the hotel.
What'll you need for this tutorial:
I hope you're excited about this project. Let's get started.
The Github repositories for this project can be found here: Front-end Backend
The Strapi documentation says that "Strapi is a flexible, open-source Headless CMS that gives developers the freedom to choose their favorite tools and frameworks while also allowing editors to manage and distribute their content easily."
Strapi enables the world's largest companies to accelerate content delivery while building beautiful digital experiences by making the admin panel and API extensible through a plugin system.
The documentation works you through installing Strapi from the CLI, the minimum requirements for running Strapi, and how to create a quickstart project.
The Quickstart project uses SQLite as the default database, but feel free to use whatever database you like.
yarn create strapi-app my-project --quickstart //using yarn
npx create-strapi-app my-project --quickstart //using npx
Replace my-project
with the name you wish to call your application directory. Your package manager will create a directory with the name and will install Strapi.
If you have followed the instructions correctly, you should have Strapi installed on your machine.
yarn develop //using yarn
npm run develop //using npm
To start our development server, Strapi starts our app on http://localhost:1337/admin
.
Now let's start building the backend of our Application.
The user we are creating here is the admin for our hotel booking system. Say an account has access to our Application and can add guests to the system.
localhost:1337/admin
. collection, types
click Users
. Add new user
username
, password
, email
, and check the click the confirm
button.plugins
, click on Content-Types builder
.create new collection type
.guests
click continue
.text
field and label it fullname
, leave the selection as short text
, then click add another field
checkIn
, leaveDate
, status
fields as they are all text
fields. Click on add another field
.paid
field, which is a boolean
value.Number
, name the field roomNo
, select integer
type.The final Guests
collection type should look like this:
Next, we'll build our rooms
collection type.
plugins
, click on Content-Types builder
create new collection type
rooms
click continue
text
field and label it type
, leave the selection as short text
, then click add another field
occupied
field, which is a boolean
value, click add another field
Number
, name the field roomNo
, select integer
type, beds
field which is also an integer
Finally, choose Relation
, name the field guests
, then select a one-to-one relationship as follows
The final version of your rooms
collection type should look like the image below.
We'll have to add some data to our rooms
collection type. Follow the instructions below to do this
collection types
, click on Rooms
add new rooms
and fill in the data appropriately.Your rooms collection type should have the following data but feel free to add as many rooms as possible.
Next, we have to set up our user permission for authenticated users.
GENERAL
in the side menuRoles
under Users and Permissions Plugin.authenticated
Permissions
, click on Application
, guests
click select all
,rooms
, click select all
save
, then go back.Your Permissions should look exactly like this:
Next, we will install and configure NuxtJs to work with our Strapi backend.
To install Nuxt.js, visit the Nuxt.js docs or run one of these commands to get started.
yarn create nuxt-app <project-name> //using yarn
npx create-nuxt-app <project-name> //using npx
npm init nuxt-app <project-name> //using npm
The command will ask you some questions (name, Nuxt options, U.I. framework, TypeScript, linter, testing framework, etc.).
We want to use Nuxt in SSR mode, Server Hosting, and Tailwind CSS as our preferred CSS framework, so select those, then choose whatever options for the rest.
Preferably leave out C.I., commit-linting, style-linting, and the rest but do whatever you like. All we'll be needing in this tutorial is what I've mentioned above.
Once all questions are answered, it will install all the dependencies. The next step is to navigate to the project folder and launch it.
cd <project-name
yarn dev //using yarn
npm run dev //using npm
We should have the Nuxt.js Application running on http://localhost:3000.
Run the following command in your terminal to open up your index page
cd pages
code index.vue
Fill up the file with the following code
1 <template>
2 <div>
3 <Loading v-if="loading" />
4 <Login v-if="!loading"/>
5 </div>
6 </template>
7 <script>
8 import Loading from '~/components/Loading.vue'
9 export default {
10 components: { Loading },
11 middleware({ $auth, redirect }) {
12 if($auth.$storage.getCookie("authenticated") === true) {
13 redirect('/dashboard/guests')
14 }
15 },
16 data() {
17 return {
18 loading: true,
19 timeout: Math.floor(Math.random() * 1000),
20 }
21 },
22 mounted() {
23 setTimeout(() => {
24 this.loading = false;
25 }, this.timeout)
26 }
27 }
28 </script>
Run the following command in your terminal to create a loading
component
cd components
touch Loading.vue
Fill up the file with the following code
1 <template>
2 <div>
3 <div class="grid items-center justify-center h-screen">
4 <div class="w-40 bg-blue-500 h-40 rounded-full"></div>
5 </div>
6 </div>
7 </template>
8 <script>
9 export default {
10 name: 'Loading'
11 }
12 </script>
13 <style scoped>
14 </style>
Building out our vuex store
Nuxt comes with a store directory pre-built for us. To activate our vuex
store, we have to create an index.js
file in the store directory as follows.
cd store
touch index.js
Fill it up with the following codes
1 export const state = () => ({
2 addGuest: false,
3 allGuests: [],
4 allRooms: []
5 })
6 export const getters = {
7 addGuest: state => state.addGuest,
8 // retrieve all guests from state
9 allGuests: state => state.allGuests,
10 // retrieve all rooms from state
11 allRooms: state => state.allRooms,
12
13 }
14 export const actions = {
15 toggleAddGuest({ commit }) {
16 commit('toggleAddGuest')
17 }
18 }
19 export const mutations = {
20 toggleAddGuest(state) {
21 state.addGuest = !state.addGuest
22 },
23 fetchGuests(state, guests) {
24 state.allGuests = guests
25 },
26 fetchRooms(state, rooms) {
27 state.allRooms = rooms
28 },
29 updateGuests(state, guest) {
30 state.allGuests.unshift(guest)
31 },
32 updateRoom(state, data) {
33 const { guest, active, room } = data
34 const curRoom = state.allRooms.find(el =>
35 el.id === room.id
36 )
37 state.allRooms[curRoom].guest = guest
38 state.allRooms[curRoom].active = active
39 }
40
41 }
We'll build out the login
component, but first, we need to install the @nuxtjs/strapi
package to assist us with that.
Installing @nuxtjs/strapi module Run the following commands in order to install the module
yarn add @nuxtjs/strapi //using yarn
npm install @nuxtjs/strapi //using npm
Open up your nuxt.config.js
file and add the following
1 export default {
2 modules: [
3 //.....
4 '@nuxtjs/strapi'],
5 strapi: {
6 entities: ['guests', 'rooms']
7 }
8 }
Now we can use the module in our code as follows
1 await this.$strapi.login({ identifier: '', password: '' }) //for login
We'll also install the @nuxtjs/auth package for added protection on the front-end of our Application
Run the following commands in order to install the module
yarn add @nuxtjs/auth-next //using yarn
npm install @nuxtjs/auth-next //using npm
Open up your nuxt.config.js
file and add the following
1 modules: [
2 //...
3 '@nuxtjs/auth-next'
4 ],
In order to create a Login
component, run the following commands
cd components
touch Login.vue
Fill it up with the following code
1 <template>
2 <div class="grid w-4/5 md:w-3/5 mx-auto items-center justify-center h-screen">
3 <div class="w-full">
4 <h1 class="font-black text-6xl mb-10">Welcome Admin</h1>
5 <form @submit="login">
6 <div class="">
7 <label for="username" class="w-full my-3">Username</label>
8 <input id="username" v-model="username" placeholder="Enter Username" type="text" class="w-full my-3 p-3 border-2">
9 </div>
10 <div class="">
11 <label for="password" class="my-3">Password</label>
12 <span class="">
13 <font-awesome-icon v-if="!passwordVisible" :icon="['fas', 'eye']" class="cursor-pointer w-5" @click='switchVisibility' />
14 <font-awesome-icon v-if="passwordVisible" :icon="['fas', 'eye-slash']" class="cursor-pointer text-gray-400 w-5" @click='switchVisibility' />
15 </span>
16 <div class="">
17 <input
18 id="password"
19 v-model="password" placeholder="Enter password"
20 :type="passwordFieldType"
21 class="my-3 p-3 border-2 w-full"
22 >
23
24 </div>
25 </div>
26 <button type="submit" class="flex items-center justify-center p-4 bg-blue-500 text-white my-3 rounded-lg">
27 Login <font-awesome-icon class="mx-3" :icon="['fas', 'arrow-right']" />
28 </button>
29 </form>
30 </div>
31 </div>
32
33 </template>
34 <script>
35 export default {
36 name: 'Login',
37 middleware({ $auth }) {
38 if($auth.$storage.getCookie("authenticated") === true) {
39 redirect('/dashboard/guest')
40 }
41 },
42 data() {
43 return {
44 username: '',
45 password: '',
46 passwordFieldType: 'password',
47 passwordVisible: false
48 }
49 },
50 methods: {
51 async login(e) {
52 e.preventDefault()
53 try {
54 await this.$strapi.login({ identifier: this.username, password: this.password })
55 if(this.$strapi.user) {
56 this.$auth.$storage.setCookie("authenticated", true)
57 this.$router.push('/dashboard/guests')
58 }
59 } catch (e) {
60 alert('Wrong credentials', e)
61 }
62
63 },
64 switchVisibility() {
65 this.passwordFieldType = this.passwordFieldType === 'password' ? 'text' : 'password'
66 this.passwordVisible = !this.passwordVisible
67 }
68 }
69 }
70 </script>
71 <style scoped>
72 </style>
What we're doing here is just enabling the Admin user to Login to our Application. On success, we redirect the user to the dashboard/guest
page to perform admin functionalities. Then in the middleware function, we check if the user is logged in or not before granting access to the dashboard/guests
page.
Building our SideNav
component
To create a SideNav
component, run the following commands
cd components
touch SideNav.vue
Fill it up with the following code
1 <template>
2 <div>
3 <!-- side nav -->
4 <div class=" ">
5 <div class="">
6 <NuxtLogo class="w-20 mx-auto" />
7 </div>
8 <div class="text-white text-center w-full mb-5">
9
10 <NuxtLink to="/dashboard">
11 <div ref="index" class="p-5 w-1/3 text-xl mx-auto rounded-full">
12 <font-awesome-icon :icon="['fas', 'home']" />
13 </div>
14 <p class="text-sm text-white">Home</p>
15 </NuxtLink>
16 </div>
17
18 <div class="text-white text-center w-full mb-5">
19 <NuxtLink to="/dashboard/guests">
20 <div ref="guests" class="p-5 w-1/3 text-xl mx-auto rounded-full">
21 <font-awesome-icon :icon="['fas', 'users']" />
22 </div>
23 <p class="text-sm text-white">Guests</p>
24 </NuxtLink>
25 </div>
26 <div class="text-white text-center w-full my-5">
27 <NuxtLink to="/dashboard/rooms">
28 <div ref="rooms" class="w-1/3 text-xl mx-auto rounded-full p-5">
29 <font-awesome-icon :icon="['fas', 'bed']" />
30 </div>
31 <p class="text-sm text-white">Rooms</p>
32 </NuxtLink>
33 </div>
34 <div class="text-white text-center cursor-pointer w-full my-7">
35 <p @click="logout">Logout</p>
36 </div>
37 </div>
38 <!-- end of first div -->
39 </div>
40 </template>
41 <script>
42 export default {
43 name: 'SideNav',
44 props: ['page'],
45 methods: {
46 async logout() {
47 await this.$strapi.logout()
48 .then(() => {
49 this.$auth.$storage.setCookie("authenticated", false)
50 this.$nuxt.$router.push('/')
51 })
52
53 }
54 },
55 mounted() {
56
57 // get current link item
58 const item = this.$refs[this.page]
59 // change it's class
60 const addClass = ['bg-purple-800', 'shadow-xl']
61 addClass.forEach(e => {
62 item.classList.add(e)
63 });
64 }
65 }
66 </script>
67 <style scoped>
68 </style>
The SideNav
component hold our logout
method which calls the
this.$strapi.logout()
method.
Building the dashboard/index
page
Run the following commands to create a pages/dashboard/index
page
cd pages
mkdir dashboard
cd dashboard
touch index.vue
Fill it up with the following code
1 <template>
2 <div>
3 <div class="flex w-screen flex-row">
4 <!-- first div here -->
5 <!-- side menu -->
6
7 <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
8 <!-- main display -->
9 <!-- second div here -->
10 <div class="w-full overflow-x-hidden md:w-5/6 h-screen">
11 <div class="w-screen h-10 m-8">
12 <h1 class="font-black">Welcome, user</h1>
13 </div>
14 <div class="w-4/5 mx-auto min-h-screen">
15 <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
16 <div class="mb-10 sm:mb-0 w-full rounded-xl p-28 bg-pink-300">
17
18 </div>
19 <div class="mb-10 sm:mb-0 w-full rounded-xl p-32 h-full bg-green-300">
20
21 </div>
22 </div>
23 <div class='w-full rounded-xl mb-20 p-32 h-full bg-blue-300'>
24 </div>
25 </div>
26 </div>
27 <!-- end of second div -->
28 </div>
29 </div>
30 </template>
31 <script>
32 export default {
33 data() {
34 return {
35 routeName: 'index'
36 }
37 },
38 }
39 </script>
40 <style scoped>
41 </style>
Building the dashboard/rooms
page
Run the following commands to create a pages/dashboard/rooms
page
cd pages
cd dashboard
touch rooms.vue
Fill it up with the following code
1 <template>
2 <div>
3 <div class="flex w-screen flex-row">
4 <!-- first div here -->
5 <!-- side menu -->
6
7 <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
8 <!-- main display -->
9 <!-- second div here -->
10 <div class="w-full overflow-x-hidden md:w-5/6 h-screen">
11 <div class="w-screen h-10 m-8">
12 <h1 class="font-black">All Rooms</h1>
13 </div>
14 <div class="w-4/5 mx-auto min-h-screen">
15 <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
16 <div class="mb-10 sm:mb-0 w-full rounded-xl p-28 bg-pink-300">
17
18 </div>
19 <div class="mb-10 sm:mb-0 w-full rounded-xl p-32 h-full bg-green-300">
20
21 </div>
22 </div>
23 <div class='w-full rounded-xl mb-20 p-32 h-full bg-blue-300'>
24 </div>
25 </div>
26 </div>
27 <!-- end of second div -->
28 </div>
29 </div>
30 </template>
31 <script>
32 export default {
33 data() {
34 return {
35 routeName: 'rooms'
36 }
37 },
38 }
39 </script>
40 <style scoped>
41 </style>
Building the dashboard/guests
page
Run the following commands to create a pages/dashboard/guests
page
cd pages
cd dashboard
touch guests.vue
Fill it up with the following code
1 <template>
2 <div>
3 <div ref="guest" class="flex w-screen flex-row">
4 <!-- first div here -->
5 <!-- side menu -->
6
7 <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
8 <!-- main display -->
9 <!-- second div here -->
10 <div class="w-full relative overflow-x-hidden md:w-5/6 h-screen">
11 <div v-if="addGuest" class="w-screen h-full top-0 bottom-0 z-10 fixed bg-opacity-30 bg-gray-300">
12 <AddGuest class="z-10 top-5 left-0 overflow-y-scroll right-0 shadow-2xl bottom-5 bg-white mx-auto fixed" />
13 </div>
14 <div class="w-screen h-10 m-8">
15 <h1 class="text-2xl text-gray-500 font-black">Manage Guests</h1>
16 </div>
17 <div class="w-4/5 mx-auto min-h-screen">
18 <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
19 <!-- active users -->
20 <div class="mb-10 sm:mb-0 shadow-2xl grid grid-cols-2 items-center justify-center gap-6 text-white w-full rounded-xl p-8 lg:p-16 bg-pink-500">
21 <font-awesome-icon class="text-6xl lg:text-8xl" :icon="['fas', 'users']" />
22 <div class="text-2xl font-bold">
23 <p>Over {{ allGuests.length }} Guests Lodged </p>
24 </div>
25 </div>
26
27 <!-- messages -->
28 <div class="">
29 <div class="my-3 font-black">
30 <font-awesome-icon :icon="['fas', 'bell']" /> Notifications
31 </div>
32 <div class="mb-10 sm:mb-0 w-full divide-y divide-white text-sm relative rounded-xl text-white p-5 h-32 overflow-y-scroll bg-green-500">
33 <p class="p-2">
34 <font-awesome-icon :icon="['fas', 'circle']" />
35 Alexander Godwin checked just checked into room 43
36 </p>
37
38 <p class="p-2">
39 <font-awesome-icon :icon="['fas', 'circle']" />
40 Alexander Godwin checked just checked into room 43
41 </p>
42
43 </div>
44 </div>
45 </div>
46
47 <!-- table part -->
48 <div class="w-full grid grid-cols-2 space-between items-center">
49 <h1 class="my-5 text-2xl font-black">All Guests</h1>
50
51 <div class="text-right">
52
53 <button class="p-3 text-white rounded-md bg-gray-500" @click="toggleAddGuest">
54 Add Guest <font-awesome-icon class="ml-2" :icon="['fas', 'plus']" />
55 </button>
56 </div>
57 </div>
58
59 <div class='w-full rounded-xl overflow-x-scroll mb-20 min-h-full bg-white'>
60 <div class="table w-full">
61 <div class="w-full table-row-group">
62
63 <!-- heading row -->
64 <div class="table-row bg-black rounded-xl text-white">
65 <div class="table-cell">
66 <div class="m-3">Name</div>
67 </div>
68 <div class="table-cell">
69 <div class="m-3">Room NO.</div>
70 </div>
71 <div class="table-cell">
72 <div class="m-3">Status</div>
73 </div>
74 <div class="table-cell">
75 <div class="m-3">Paid</div>
76 </div>
77 <div class="table-cell">
78 <div class="m-3">Checked In</div>
79 </div>
80 <div class="table-cell">
81 <div class="m-3">Leave Date</div>
82 </div>
83 <div class="table-cell">
84 <div class="m-3">Action</div>
85 </div>
86 </div>
87 <!-- end of heading row -->
88
89 <div v-for="(guest, i) in allGuests" :key="i" class="table-row bg-gray-500 text-white">
90
91 <div class="table-cell">
92 <div class="m-3">{{ guest.fullname }}</div>
93 </div>
94 <div class="table-cell">
95 <div class="m-3">{{ guest.roomNo }}</div>
96 </div>
97 <div class="table-cell">
98 <div class="m-3">{{ guest.status }}</div>
99 </div>
100 <div class="table-cell">
101 <div class="m-3">{{ guest.paid }}</div>
102 </div>
103 <div class="table-cell">
104 <div class="m-3">{{ guest.checkIn }} </div>
105 </div>
106 <div class="table-cell">
107 <div class="m-3">{{ guest.leaveDate }}</div>
108 </div>
109 <div>
110 <button v-if="guest.status === 'active'" @click="checkOut(guest)" class="p-2 m-3 bg-green-500">
111 check-out
112 </button>
113 </div>
114
115 </div>
116
117 </div>
118 </div>
119 </div>
120 </div>
121 </div>
122 <!-- end of second div -->
123 </div>
124 </div>
125 </template>
126 <script>
127 import { mapActions, mapGetters } from 'vuex'
128 export default {
129 middleware({ $strapi, $auth, redirect }) {
130 if($auth.$storage.getCookie("authenticated") === false) {
131 redirect('/')
132 }
133 },
134 async asyncData({ $strapi, store }) {
135 const guests = await $strapi.$guests.find({
136 _sort: 'published_at:DESC'
137 })
138 store.commit('fetchGuests', guests)
139 const rooms = await $strapi.$rooms.find()
140 store.commit('fetchRooms', rooms)
141 },
142 data() {
143 return {
144 routeName: 'guests',
145 }
146 },
147 methods: {
148 ...mapActions(['toggleAddGuest']),
149 async checkOut(guest) {
150
151 await this.$strapi.$rooms.update(guest.roomNo, {
152 guests: null,
153 occupied: false
154 })
155 const rooms = await this.$strapi.$rooms.find()
156 this.$store.commit('fetchRooms', rooms)
157 await this.$strapi.$guests.update(guest.id, {
158 status: 'inactive'
159 })
160 const guests = await this.$strapi.$guests.find({
161 _sort: 'published_at:DESC'
162 })
163 this.$store.commit('fetchGuests', guests)
164 },
165
166 },
167 computed: {
168 ...mapGetters(['addGuest', 'allGuests', 'allRooms'])
169 },
170 }
171 </script>
172 <style scoped>
173 </style>
This guests
page has most of our functionality, as it allows us to:
guests
from our Strapi API and push them to our vuex storerooms
from our Strapi API and push them to the vuex storeguest
from the hotel.When a guest is checked-out from the hotel their status becomes inactive and the check out button is not displayed for them.
add guest
componentWe'll build the component to add the guest to a free hotel room in our database. Run the following commands to create a AddGuests
component
cd components
touch AddGuests.vue
Fill it up with the following code
1 <template>
2 <div class="p-10 shadow-xl shadow-yellow-300 w-3/4 sm:w-1/2 rounded-xl max-h-screen">
3 <div class="m-2">
4 <h1 class="font-black text-yellow-800">New Guest</h1>
5 </div>
6 <div>
7 <form @submit.prevent="newGuest">
8 <div class="p-2 text-sm">
9 <label class="font-bold text-yellow-800 text-sm" for="checkin">FullName</label>
10 <input v-model="fullname" type="text" class="py-3 pr-3 w-full border-b-2 border-gray-200 focus:outline-none hover:border-yellow-300">
11 </div>
12 <div class="p-2 text-sm">
13 <label class="font-bold text-yellow-800 text-sm" for="checkin">Email</label>
14 <input v-model="email" type="email" class="py-3 pr-3 w-full border-b-2 border-gray-200 focus:outline-none hover:border-yellow-300">
15 </div>
16 <!-- check in and check out -->
17 <div class="p-2">
18 <div class="mb-3 text-sm">
19 <label class="font-bold text-yellow-800" for="checkin">Check In</label>
20 <input v-model="checkIn" id="checkin" class="py-3 pr-3 w-full border-b-2 border-gray-200 focus:outline-none hover:border-yellow-300" type="date">
21 </div>
22 <div class="text-sm">
23 <label class="font-bold text-yellow-800 text-sm" for="leavedate">Leave Date</label>
24 <input v-model="leaveDate" id="leavedate" class="py-3 pr-3 w-full border-b-2 border-gray-200 focus:outline-none hover:border-yellow-300" type="date">
25 </div>
26 </div>
27
28 <div class="text-sm p-2">
29 <label for="rooms" class="text-sm text-yellow-800 font-bold">Select Room</label>
30 <select v-model="RoomNo" id="rooms" name="" class="py-3 pr-3 w-full border-b-2 border-gray-200 focus:outline-none hover:border-yellow-300">
31 <option v-for="(room, i) in allRooms.filter(el => el.occupied === false).map(el => el.roomNo)" :key="i" :value="room">{{ room }}</option>
32 </select>
33 </div>
34 <div class="my-3">
35 <button class="p-3 bg-green-500 text-white" type="submit">
36 Submit
37 </button>
38 <button class="p-3 bg-red-500 text-white" @click.prevent="toggleAddGuest">
39 Cancel
40 </button>
41 </div>
42
43 </form>
44 </div>
45 </div>
46 </template>
47 <script>
48 import { mapActions, mapGetters } from 'vuex'
49 export default {
50 name: 'AddGuest',
51 data() {
52 return {
53 fullname: '',
54 RoomNo: '',
55 checkIn: '',
56 leaveDate: '',
57 email: ''
58 }
59 },
60 computed: {
61 ...mapGetters(['allRooms'])
62 },
63 methods: {
64 async newGuest() {
65 const newGuest = {
66 fullname: this.fullname,
67 checkIn: this.checkIn,
68 leaveDate: this.leaveDate,
69 roomNo: parseInt(this.RoomNo),
70 status: 'active',
71 paid: true,
72 email: this.email
73 }
74 const { guest } = await this.$strapi.$guests.create(newGuest)
75 console.log("guest", guest)
76 await this.$strapi.$rooms.update(this.RoomNo, {
77 occupied: true,
78 guest
79 })
80 this.$store.commit('updateGuests', guest)
81 const rooms = await this.$strapi.$rooms.find()
82 this.$store.commit('fetchRooms', rooms)
83 this.toggleAddGuest()
84 },
85 ...mapActions(['toggleAddGuest',])
86 }
87 }
88 </script>
89 <style scoped>
90 </style>
The result of the code above. I have just one empty room left in my database since the other two rooms are occupied, so I can only select room 2.
Next, we'll build a feature into our backend that allows us to send emails to our guests once they are added to our database.
To set up emails with Nodemailer, open up the folder with your Strapi code in your favorite code editor. Then follow the instructions below:
Installing Nodemailer To install nodemailer, run the following command from your terminal.
yarn add nodemailer //using yarn
npm install nodemailer //using npm
Once that's done, run the following commands to create an api/emails/services/Email.js
file in your Strapi code base
1 cd api
2 mkdir emails
3 cd emails
4 mkdir services
5 cd services
6 touch Email.js
Open up the Email.js
file and fill it up with the following code
1 const nodemailer = require('nodemailer');
2 const transporter = nodemailer.createTransport({
3 service: 'gmail',
4 port: '465',
5 secure: true,
6 host: 'smtp.gmail.com',
7 auth: {
8 user: process.env.USERNAME,
9 pass: process.env.PASSWORD,
10 },
11 });
12 module.exports = {
13 send: (from, to, subject, text) => {
14 // Setup e-mail data.
15 const options = {
16 from,
17 to,
18 subject,
19 text,
20 };
21 // Return a promise of the function that sends the email.
22 return transporter.sendMail(options);
23 },
24 };
Next up locate the .env/example
file and rename it to .env
the add the following lines.
1 USERNAME=<YOUR_EMAIL>
2 PASSWORD=<YOUR_PASSWORD>
Remember to replace both <YOUR_EMAIL>
and <YOUR_PASSWORD>
with your actual email address and password.
In order to use the email function in our code, open up the api/guests/controllers/guest.js
file and fill it up with the following code
1 'use strict';
2 /**
3 * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
4 * to customize this controller
5 */
6 module.exports = {
7 async create(ctx) {
8
9 let { fullname: name, email, roomNo, checkIn, leaveDate } = ctx.request.body
10
11 strapi.services.email.send(
12 'alecgee73@gmail.com',
13 email,
14 'Room booked successfully',
15 `Hello ${name}, Welcome to Mars hotel,you booked ${roomNo} from ${checkIn} to ${leaveDate} enjoy yourself`
16 )
17 .then((res) => console.log(res))
18 .catch(err => console.log(err))
19 ctx.send({
20 guest : await strapi.query('guests').create(ctx.request.body)
21 })
22
23 }
24 };
What we're doing here is modifying the Strapi to create
a function to do the following;
strapi.services.email.send()
function, to send an email to our new guest,strapi.query().create()
function, ctx.send()
function.Now whenever a new guest is created, they will receive an email from the hotel booking system.
What we've seen so far is how to create a simple hotel booking system using Strapi. You could do more, add more features and tailor the Application according to your needs. No matter what front-end framework you are using, the Strapi logic remains the same, and that's nice, Be sure to do more and explore.
Alexander Godwin is a Software Developer and writer that likes to write code and build things. Learning by doing is the best way and it's how Alex helps others learn. Follow him on Twitter (@oviecodes)