How to Build a Booking System with Strapi and Nuxt

Shada - Aug 19 '21 - - Dev Community

In this tutorial, we will learn how to build a simple hotel booking system with Strapi and NuxtJs. 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.

Prerequisites

What'll you need for this tutorial:

Table of Contents:

  • Getting started with Strapi
  • Setting up our Strapi backend
  • Setting users permission
  • Building the front-end
  • Using the Nuxt-Strapi module for authentication
  • Setting up emails using Nodemailer

What you’ll Build

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

Getting Started with Strapi

What is Strapi?

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.

Installing Strapi

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

To start our development server, Strapi starts our app on http://localhost:1337/admin.


Setting up our Strapi backend

Now let's start building the backend of our Application.

Creating the hotel Admin user for 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.

  • Open up your Strapi admin panel in your browser by visiting localhost:1337/admin.
  • On the sidebar under collection, types click Users.
  • Click Add new user
    • Then add a username, password, email, and check the click the confirm button.

Building our Guests collection type

  • On the sidebar under plugins, click on Content-Types builder.
  • Click on create new collection type.
  • Fill in the display name as guests click continue.
    • Select the text field and label it fullname, leave the selection as short text, then click add another field
    • Repeat the procedure to create the checkIn, leaveDate, status fields as they are all text fields. Click on add another field.
    • Next, we add the paid field, which is a boolean value.
    • Finally, select Number, name the field roomNo, select integer type.

The final Guests collection type should look like this:

Building our Rooms collection type

Next, we'll build our rooms collection type.

  • On the sidebar under plugins, click on Content-Types builder
  • Click on create new collection type
  • Fill in the display name as rooms click continue
    • Select the text field and label it type, leave the selection as short text, then click add another field
    • Next, we add the occupied field, which is a boolean value, click add another field
    • Select Number, name the field roomNo, select integer type,
    • Repeat the previous instruction to create a 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.

Seeding the database

We'll have to add some data to our rooms collection type. Follow the instructions below to do this

  • In your Strapi admin panel, under collection types, click on Rooms
  • Click 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.

Setting user permission

  1. Click on Settings under GENERAL in the side menu
  2. Click on Roles under Users and Permissions Plugin.
  3. It will display a list of roles. Click on authenticated
  4. Scroll down, under Permissions, click on Application,
  5. Under guests click select all,
  6. Under rooms, click select all
  7. Click save, then go back.

Your Permissions should look exactly like this:

Installing Nuxt.js

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We should have the Nuxt.js Application running on http://localhost:3000.

Building our Index page

Run the following command in your terminal to open up your index page

    cd pages
    code index.vue
Enter fullscreen mode Exit fullscreen mode

Fill up the file with the following code

    <template>
      <div>
        <Loading v-if="loading" />
        <Login v-if="!loading"/>
      </div>
    </template>
    <script>
    import Loading from '~/components/Loading.vue'
    export default {
      components: { Loading },
      middleware({ $auth, redirect }) {
        if($auth.$storage.getCookie("authenticated") === true) {
            redirect('/dashboard/guests')
        }
      },
      data() {
        return {
          loading: true,
          timeout: Math.floor(Math.random() * 1000),
        }
      },
      mounted() {
        setTimeout(() => {
          this.loading = false;
        }, this.timeout)
      }
    }
    </script>
Enter fullscreen mode Exit fullscreen mode

Building the loading component

Run the following command in your terminal to create a loading component

    cd components
    touch Loading.vue
Enter fullscreen mode Exit fullscreen mode

Fill up the file with the following code

    <template>
        <div>
            <div class="grid items-center justify-center h-screen">
                <div class="w-40 bg-blue-500 h-40 rounded-full"></div>
            </div>
        </div>
    </template>
    <script>
        export default {
            name: 'Loading'
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following codes

    export const state = () => ({
        addGuest: false,
        allGuests: [],
        allRooms: []
    })
    export const getters = {
        addGuest: state => state.addGuest,
        // retrieve all guests from state
        allGuests: state => state.allGuests,
        // retrieve all rooms from state
        allRooms: state => state.allRooms,

    }
    export const actions = {
        toggleAddGuest({ commit }) {
            commit('toggleAddGuest')
        }
    }
    export const mutations = {
        toggleAddGuest(state) {
            state.addGuest = !state.addGuest
        },
        fetchGuests(state, guests) {
            state.allGuests = guests
        },
        fetchRooms(state, rooms) {
            state.allRooms = rooms
        },
        updateGuests(state, guest) {
            state.allGuests.unshift(guest)
        },
        updateRoom(state, data) {
            const { guest, active, room } = data
            const curRoom = state.allRooms.find(el => 
                el.id === room.id
            )
            state.allRooms[curRoom].guest = guest
            state.allRooms[curRoom].active = active
        }

    }
Enter fullscreen mode Exit fullscreen mode

We'll build out the login component, but first, we need to install the @nuxtjs/strapi package to assist us with that.

Using the @Nuxtjs/Strapi module for authentication

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
Enter fullscreen mode Exit fullscreen mode

Open up your nuxt.config.js file and add the following

    export default {
      modules: [
        //.....
        '@nuxtjs/strapi'],
      strapi: {
        entities: ['guests', 'rooms']
      }
    }
Enter fullscreen mode Exit fullscreen mode

Now we can use the module in our code as follows

    await this.$strapi.login({ identifier: '', password: '' }) //for login
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Open up your nuxt.config.js file and add the following

     modules: [
        //...
        '@nuxtjs/auth-next'
      ],
Enter fullscreen mode Exit fullscreen mode

Building the login component

In order to create a Login component, run the following commands

    cd components
    touch Login.vue
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div class="grid w-4/5 md:w-3/5 mx-auto items-center justify-center h-screen">
            <div class="w-full">
                <h1 class="font-black text-6xl mb-10">Welcome Admin</h1>
                <form @submit="login">
                    <div class="">
                        <label for="username" class="w-full my-3">Username</label>
                        <input id="username" v-model="username" placeholder="Enter Username" type="text" class="w-full my-3 p-3 border-2">
                    </div>
                    <div class="">
                        <label for="password" class="my-3">Password</label> 
                        <span class=""> 
                            <font-awesome-icon v-if="!passwordVisible" :icon="['fas', 'eye']" class="cursor-pointer w-5" @click='switchVisibility' />
                            <font-awesome-icon v-if="passwordVisible" :icon="['fas', 'eye-slash']" class="cursor-pointer text-gray-400 w-5" @click='switchVisibility' />
                        </span>
                        <div class=""> 
                            <input 
                                id="password" 
                                v-model="password" placeholder="Enter password" 
                                :type="passwordFieldType" 
                                class="my-3 p-3 border-2 w-full"
                            >

                        </div>
                    </div>
                    <button type="submit" class="flex items-center justify-center p-4 bg-blue-500 text-white my-3 rounded-lg">
                        Login <font-awesome-icon class="mx-3" :icon="['fas', 'arrow-right']" />
                    </button>              
                </form>
            </div>
        </div>

    </template>
    <script>
        export default {
            name: 'Login',
            middleware({ $auth }) {
                if($auth.$storage.getCookie("authenticated") === true) {
                    redirect('/dashboard/guest')
                }
            },
            data() {
                return {
                    username: '',
                    password: '',
                    passwordFieldType: 'password',
                    passwordVisible: false
                }
            },
            methods: {
                async login(e) {
                    e.preventDefault()
                    try {
                        await this.$strapi.login({ identifier: this.username, password: this.password })
                        if(this.$strapi.user)  {
                            this.$auth.$storage.setCookie("authenticated", true)
                            this.$router.push('/dashboard/guests')
                        }
                    } catch (e) {
                      alert('Wrong credentials', e)
                    }

                },
                switchVisibility() {
                    this.passwordFieldType = this.passwordFieldType === 'password' ? 'text' : 'password'
                    this.passwordVisible = !this.passwordVisible
                }
            }
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div>
            <!-- side nav -->
            <div class=" ">
                <div class="">
                    <NuxtLogo class="w-20 mx-auto" />
                </div>
                <div class="text-white text-center w-full mb-5">

                    <NuxtLink to="/dashboard">
                        <div ref="index" class="p-5 w-1/3 text-xl mx-auto rounded-full">
                            <font-awesome-icon :icon="['fas', 'home']" />
                        </div>
                        <p class="text-sm text-white">Home</p>
                    </NuxtLink>
                </div>

                <div class="text-white text-center w-full mb-5">
                    <NuxtLink to="/dashboard/guests">
                        <div ref="guests" class="p-5 w-1/3 text-xl mx-auto rounded-full">
                            <font-awesome-icon :icon="['fas', 'users']" />
                        </div>
                        <p class="text-sm text-white">Guests</p>
                    </NuxtLink>
                </div>
                <div class="text-white text-center w-full my-5">
                    <NuxtLink to="/dashboard/rooms">
                        <div ref="rooms"  class="w-1/3 text-xl mx-auto rounded-full p-5">
                            <font-awesome-icon :icon="['fas', 'bed']" /> 
                        </div>
                        <p class="text-sm text-white">Rooms</p>
                    </NuxtLink>
                </div>
                <div class="text-white text-center cursor-pointer w-full my-7">
                    <p @click="logout">Logout</p>
                </div>
            </div>
            <!-- end of first div -->
        </div>
    </template>
    <script>
        export default {
            name: 'SideNav',
            props: ['page'],
            methods: {
                async logout() {
                    await this.$strapi.logout()
                    .then(() => {
                        this.$auth.$storage.setCookie("authenticated", false)
                        this.$nuxt.$router.push('/')
                    })

                }
            },
            mounted() {

                // get current link item
                const item = this.$refs[this.page]
                // change it's class
                const addClass = ['bg-purple-800', 'shadow-xl']
                addClass.forEach(e => {
                    item.classList.add(e)
                });
            }
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div>
            <div class="flex w-screen flex-row">
                <!-- first div here    -->
                <!-- side menu -->

                <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
                <!-- main display -->
                <!-- second div here -->
                <div class="w-full overflow-x-hidden md:w-5/6 h-screen">
                    <div class="w-screen h-10 m-8">
                        <h1 class="font-black">Welcome, user</h1>
                    </div>
                    <div class="w-4/5 mx-auto min-h-screen">
                        <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
                            <div class="mb-10 sm:mb-0 w-full rounded-xl p-28 bg-pink-300">

                            </div>
                            <div class="mb-10 sm:mb-0 w-full rounded-xl p-32 h-full bg-green-300">

                            </div>
                        </div>
                        <div class='w-full rounded-xl mb-20 p-32 h-full bg-blue-300'>
                        </div>
                    </div>
                </div>
                <!-- end of second div -->
            </div>
        </div>
    </template>
    <script>
        export default {
            data() {
                return {
                    routeName: 'index'
                }
            },
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

Building the dashboard/rooms page
Run the following commands to create a pages/dashboard/rooms page

    cd pages
    cd dashboard
    touch rooms.vue
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div>
            <div class="flex w-screen flex-row">
                <!-- first div here    -->
                <!-- side menu -->

                <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
                <!-- main display -->
                <!-- second div here -->
                <div class="w-full overflow-x-hidden md:w-5/6 h-screen">
                    <div class="w-screen h-10 m-8">
                        <h1 class="font-black">All Rooms</h1>
                    </div>
                    <div class="w-4/5 mx-auto min-h-screen">
                        <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
                            <div class="mb-10 sm:mb-0 w-full rounded-xl p-28 bg-pink-300">

                            </div>
                            <div class="mb-10 sm:mb-0 w-full rounded-xl p-32 h-full bg-green-300">

                            </div>
                        </div>
                        <div class='w-full rounded-xl mb-20 p-32 h-full bg-blue-300'>
                        </div>
                    </div>
                </div>
                <!-- end of second div -->
            </div>
        </div>
    </template>
    <script>
        export default {
            data() {
                return {
                    routeName: 'rooms'
                }
            },
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

Building the dashboard/guests page
Run the following commands to create a pages/dashboard/guests page

    cd pages
    cd dashboard
    touch guests.vue
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div>
            <div ref="guest" class="flex w-screen flex-row">
                <!-- first div here    -->
                <!-- side menu -->

                <SideNav class="hidden sm:block sm:w-1/6 h-screen bg-purple-600" :page="routeName" />
                <!-- main display -->
                <!-- second div here -->
                <div class="w-full relative overflow-x-hidden md:w-5/6 h-screen">
                    <div v-if="addGuest" class="w-screen h-full top-0 bottom-0 z-10 fixed bg-opacity-30 bg-gray-300">
                       <AddGuest class="z-10 top-5 left-0 overflow-y-scroll right-0 shadow-2xl bottom-5 bg-white mx-auto fixed" /> 
                    </div>
                    <div class="w-screen h-10 m-8">
                        <h1 class="text-2xl text-gray-500 font-black">Manage Guests</h1>
                    </div>
                    <div class="w-4/5 mx-auto min-h-screen">
                        <div class="block sm:grid sm:grid-cols-2 sm:gap-4 mb-10 items-center justify-center">
                            <!-- active users -->
                            <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">
                                <font-awesome-icon class="text-6xl lg:text-8xl" :icon="['fas', 'users']" />
                                <div class="text-2xl font-bold">
                                    <p>Over {{ allGuests.length }} Guests Lodged </p>
                                </div>
                            </div>

                            <!-- messages -->
                            <div class="">
                                <div class="my-3 font-black">
                                    <font-awesome-icon :icon="['fas', 'bell']" /> Notifications
                                </div>
                                <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">
                                    <p class="p-2">
                                       <font-awesome-icon :icon="['fas', 'circle']" />
                                       Alexander Godwin checked just checked into room 43
                                    </p>

                                    <p class="p-2">
                                       <font-awesome-icon :icon="['fas', 'circle']" />
                                       Alexander Godwin checked just checked into room 43
                                    </p>

                                </div>
                            </div>
                        </div>

                        <!-- table part -->
                        <div class="w-full grid grid-cols-2 space-between items-center">
                          <h1 class="my-5 text-2xl font-black">All Guests</h1>

                          <div class="text-right">

                            <button class="p-3 text-white rounded-md bg-gray-500" @click="toggleAddGuest">
                              Add Guest <font-awesome-icon class="ml-2" :icon="['fas', 'plus']" />
                          </button>
                          </div>
                        </div>

                        <div class='w-full rounded-xl overflow-x-scroll mb-20 min-h-full bg-white'>
                            <div class="table w-full">
                                <div class="w-full table-row-group">

                                   <!-- heading row -->
                                    <div class="table-row bg-black rounded-xl text-white">
                                        <div class="table-cell">
                                            <div class="m-3">Name</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Room NO.</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Status</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Paid</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Checked In</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Leave Date</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">Action</div>
                                        </div>
                                    </div>
                                    <!-- end of heading row -->

                                    <div v-for="(guest, i) in allGuests" :key="i" class="table-row bg-gray-500 text-white">

                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.fullname }}</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.roomNo }}</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.status }}</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.paid }}</div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.checkIn }} </div>
                                        </div>
                                        <div class="table-cell">
                                            <div class="m-3">{{ guest.leaveDate }}</div>
                                        </div>
                                        <div>
                                            <button v-if="guest.status === 'active'" @click="checkOut(guest)" class="p-2 m-3 bg-green-500">
                                                check-out
                                            </button>
                                        </div>

                                    </div>

                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- end of second div -->
            </div>
        </div>
    </template>
    <script>
        import { mapActions, mapGetters } from 'vuex'
        export default {
            middleware({ $strapi, $auth, redirect }) {
                if($auth.$storage.getCookie("authenticated") === false) {
                    redirect('/')
                }
            },
            async asyncData({ $strapi, store }) {
                const guests = await $strapi.$guests.find({
                    _sort: 'published_at:DESC'
                })
                store.commit('fetchGuests', guests)
                const rooms = await $strapi.$rooms.find()
                store.commit('fetchRooms', rooms)
            },
            data() {
                return {
                    routeName: 'guests',
                }
            },
            methods: {
                ...mapActions(['toggleAddGuest']),
                async checkOut(guest) {

                    await this.$strapi.$rooms.update(guest.roomNo, {
                        guests: null,
                        occupied: false
                    })
                    const rooms = await this.$strapi.$rooms.find()
                    this.$store.commit('fetchRooms', rooms)
                    await this.$strapi.$guests.update(guest.id, {
                        status: 'inactive'
                    })
                    const guests = await this.$strapi.$guests.find({
                        _sort: 'published_at:DESC'
                    }) 
                    this.$store.commit('fetchGuests', guests)
                },

            },
            computed: {
                ...mapGetters(['addGuest', 'allGuests', 'allRooms'])
            },
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

This guests page has most of our functionality, as it allows us to:

  • Fetch guests from our Strapi API and push them to our vuex store
  • Display all our guests at a table.
  • Fetch rooms from our Strapi API and push them to the vuex store
  • Check out a guest 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.

Building the add guest component

We'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
Enter fullscreen mode Exit fullscreen mode

Fill it up with the following code

    <template>
        <div class="p-10 shadow-xl shadow-yellow-300 w-3/4 sm:w-1/2 rounded-xl max-h-screen">
            <div class="m-2">
                <h1 class="font-black text-yellow-800">New Guest</h1>
            </div>
            <div>
                <form @submit.prevent="newGuest">
                    <div class="p-2 text-sm">
                        <label class="font-bold text-yellow-800 text-sm" for="checkin">FullName</label>
                        <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">
                    </div>
                    <div class="p-2 text-sm">
                        <label class="font-bold text-yellow-800 text-sm" for="checkin">Email</label>
                        <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">
                    </div>
                    <!-- check in and check out -->
                    <div class="p-2">
                        <div class="mb-3 text-sm">
                            <label class="font-bold text-yellow-800" for="checkin">Check In</label>
                            <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">
                        </div>
                        <div class="text-sm">
                            <label class="font-bold text-yellow-800 text-sm" for="leavedate">Leave Date</label>
                            <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">
                        </div>
                    </div>

                    <div class="text-sm p-2">
                        <label for="rooms" class="text-sm text-yellow-800 font-bold">Select Room</label>
                        <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">
                            <option v-for="(room, i) in allRooms.filter(el => el.occupied === false).map(el => el.roomNo)" :key="i" :value="room">{{ room }}</option>
                        </select>
                    </div>
                    <div class="my-3">
                        <button class="p-3 bg-green-500 text-white" type="submit">
                            Submit 
                        </button>
                        <button class="p-3 bg-red-500 text-white" @click.prevent="toggleAddGuest">
                            Cancel 
                        </button>
                    </div>

                </form>
            </div>
        </div>
    </template>
    <script>
        import { mapActions, mapGetters } from 'vuex'
        export default {
            name: 'AddGuest',
            data() {
                return {
                    fullname: '',
                    RoomNo: '',
                    checkIn: '',
                    leaveDate: '',
                    email: ''
                }
            },
            computed: {
                ...mapGetters(['allRooms'])
            },
            methods: {
                async newGuest() {
                    const newGuest = {
                        fullname: this.fullname,
                        checkIn: this.checkIn,
                        leaveDate: this.leaveDate,
                        roomNo: parseInt(this.RoomNo),
                        status: 'active',
                        paid: true,
                        email: this.email
                    }
                    const { guest } = await this.$strapi.$guests.create(newGuest)
                    console.log("guest", guest)
                    await this.$strapi.$rooms.update(this.RoomNo, {
                        occupied: true,
                        guest
                    })
                    this.$store.commit('updateGuests', guest)
                    const rooms = await this.$strapi.$rooms.find()
                    this.$store.commit('fetchRooms', rooms)
                    this.toggleAddGuest()
                },
                ...mapActions(['toggleAddGuest',])
            }
        }
    </script>
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

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.

Setting up emails using Nodemailer

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
Enter fullscreen mode Exit fullscreen mode

Once that's done, run the following commands to create an api/emails/services/Email.js file in your Strapi code base

    cd api
    mkdir emails
    cd emails
    mkdir services
    cd services
    touch Email.js
Enter fullscreen mode Exit fullscreen mode

Open up the Email.js file and fill it up with the following code

    const nodemailer = require('nodemailer');
    const transporter = nodemailer.createTransport({
      service: 'gmail',
      port: '465',
      secure: true,
      host: 'smtp.gmail.com',
      auth: {
        user: process.env.USERNAME,
        pass: process.env.PASSWORD,
      },
    });
    module.exports = {
      send: (from, to, subject, text) => {
        // Setup e-mail data.
        const options = {
          from,
          to,
          subject,
          text,
        };
        // Return a promise of the function that sends the email.
        return transporter.sendMail(options);
      },
    };
Enter fullscreen mode Exit fullscreen mode

Next up locate the .env/example file and rename it to .env the add the following lines.

    USERNAME=<YOUR_EMAIL>
    PASSWORD=<YOUR_PASSWORD>
Enter fullscreen mode Exit fullscreen mode

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

    'use strict';
    /**
     * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
     * to customize this controller
     */
    module.exports = {
        async create(ctx) {

            let { fullname: name, email, roomNo, checkIn, leaveDate } = ctx.request.body

            strapi.services.email.send(
                'alecgee73@gmail.com',
                email,
                'Room booked successfully',
                `Hello ${name}, Welcome to Mars hotel,you booked ${roomNo} from ${checkIn} to ${leaveDate} enjoy yourself`
            )
                .then((res) => console.log(res))
                .catch(err => console.log(err))
            ctx.send({
                guest : await strapi.query('guests').create(ctx.request.body)
            })

        }
    };
Enter fullscreen mode Exit fullscreen mode

What we're doing here is modifying the Strapi to create a function to do the following;

  • Call the email service we created using the strapi.services.email.send() function, to send an email to our new guest,
  • Create the new guest in our database using the strapi.query().create() function,
  • Finally returning the created guest using ctx.send() function.

Now whenever a new guest is created, they will receive an email from the hotel booking system.

Conclusion

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .