How to create an order based web app using nodejs and vue3

(Service Web App)

Mert Kadir Gursoy
16 min readJan 24, 2024

The Technology

  • Express JS ( Web Framework for NodeJS) | expressjs.com
  • Sequelize (ORM — Object to Relational Mapping) | sequelize.org
  • SQLite (SQLite is a database engine) | sqlite.org
  • Vuejs 3 (The Progressive Javascript Framework) | vuejs.org
  • Vue Router ( ) | router.vuejs.org
  • Vue Pinia ((Store library and state management framework for Vue.js )) | pinia.vuejs.org
  • Vuetify (Vue Component Framework) | vuetifyjs.com
  • Bonus: Vee Validate | vee-validate.logaretm.com

1. Create a project folder

  • Create a folder that named as i.e. “orderSystem” project folder.

ORDERSYSTEM (Project Folder)
├── frontend
│ ├── node_modules
│ ├── public
├── src
│ │ ├── assets
│ │ ├── components
│ │ │ ├── MenuComp.vue
│ │ │ ├── AboutComp.vue
│ │ │ ├── CartComp.vue
│ │ │ ├── CheckoutComp.vue
│ │ │ └── OrderComp.vue
│ │ ├── layouts
│ │ │ ├── default
│ │ │ │ ├── AppBar.vue
│ │ │ │ ├── Default.vue
│ │ │ │ └── View.vue
│ │ ├── plugins
│ │ │ └── index.js
│ │ │ └── vuetify.js
│ │ ├── router
│ │ │ └── index.js
│ │ ├── stores
│ │ │ └── app.js
│ │ │ └── index.js
│ │ ├── views
│ │ │ ├── Menu.vue
│ │ │ ├── About.vue
│ │ │ ├── Cart.vue
│ │ │ ├── Checkout.vue
│ │ │ └── Order.vue
│ │ ├── app.vue
│ │ └── main.js
│ ├── .browserslistrc
│ ├── .editorconfig
│ ├── .eslintrc-auto-import.json
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── index.html
│ ├── jsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ └── vite.config.mjs

(backend file below)
│ ├── node_modules
│ ├── database.sqlite
│ ├── db.js
│ ├── package-lock.json
│ ├── package.json
│ ├── server.js
│ └── test.http

2. Open this folder in VSCode

  • Open new terminal in VS Code from the top bar with “Terminal”
  • Now you should be in “ordersystem” folder. (check it if you are not in. If not go in it with the command cd desktop > cd ordersystem)

BACKEND ENVIRONMENT SETUP

3. Initiate NodeJS

  • Terminal command: “ npm init ” command (then press enter and skip all)

4. Install Dependencies:

Install “ExpressJS” packages. ExpressJS

  • Still we are in VSCode Terminal and in the-app folder path.
  • Run this command to install ExpressJS. “npm install -- save express

Install “Sequelize” packages.

  • Run this command to install SequelizeJS.
  • Terminal Command: “npm install -- save sequelize

Install “Sqlite3” packages.

  • Run this command to install Sqlite3.
  • Terminal Command: “npm install -- save sqlite3

BACKEND SETUP (DB & API)

We’ll create two different js file to create data in db and create the apis and use the data from db via apis.

5. Create “db.js” & “server.js” file for Database Setup and API Setup in “orderSystem” folder

* 5.1) Create the db and define the tables, data types, functions to create data and also can create dummy data.

// Database
const { Sequelize, DataTypes } = require('sequelize');


const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'database.sqlite'
});
var item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false
},
desc: {
type: DataTypes.STRING,
allowNull: false
},
state: {
type: DataTypes.ENUM(['draft', 'live', 'archived']),
defaultValue: 'live',
allowNull: false
},
price: {
type: DataTypes.INTEGER,
defaultValue: 5,
allowNull: false,
},
imageUrl: {
type: DataTypes.STRING,
allowNull: false
},
});
var order = sequelize.define('Order', {
date: {
type: DataTypes.DATE,
defaultValue: new Date(),
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false
},
phone: {
type: DataTypes.STRING,
allowNull: false
},
price: {
type: DataTypes.INTEGER,
allowNull: false,
},
});
var orderItem = sequelize.define('OrderItem', {
amount: {
type: DataTypes.INTEGER,
allowNull: false
},
price: {
type: DataTypes.INTEGER,
allowNull: false,
},
});

order.belongsToMany(item, { through: orderItem });

async function createItems() {
await sequelize.sync({ force: true });
await item.create({
name: "Latte",
desc: "A classic coffee latte with all the fixings.",
category: "Hot Drinks",
price: 12,
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-76R7O1chQfHoeMZFwjt8dNoFkjXAdAuodg&s"
});

await item.create({
name: "Frozen Orange",
desc: "A deliciously refreshing frozen orange juice",
category: "Cold Drinks",
price: 10,
imageUrl: "https://i1.pickpik.com/photos/1014/108/1022/smoothie-fruit-blend-glass-preview.jpg"
});

await item.create({
name: "Chai Tea",
desc: "Classic chai tea, an alternative taste.",
category: "Tea",
price: 5,
imageUrl: "https://hungryhealthyhappy.com/wp-content/uploads/2014/11/Chai-Tea-Latte-featured-500x500.jpg"
});

await item.create({
name: "Carrot Cake",
desc: "A scrumptious carrot cake delight.",
category: "Desserts",
price: 8,
imageUrl: "https://live.staticflickr.com/3262/2363057198_6d279c37f1_b.jpg"
});
}
exports.startDB = () => {
createItems()
}
exports.getAllItems = () => {
return item.findAll()
}
exports.getOrder = (id) => {
return order.findOne({
where: {
id: id
},
include: item
})
}
exports.createOrder = async (requestBody) => {
let price = 0;
requestBody.items.map(i => {
price += (i.item.price * i.amount)
})
requestBody.price = price
let o = await order.create(requestBody)
await orderItem.bulkCreate(requestBody.items.map(i => {
return {
OrderId: o.id,
ItemId: i.item.id,
price: i.item.price,
amount: i.amount
}
}))
return o
}

* 5.2) Create “server.js” file as in root folder.
orderSystem > server.js
The important part is defining db = requrie(‘./db) at the top and call db.startDB() funciton to use DB and create dummy products data.
=> items Table (products),
=> orders Table
=> orderItems Table(Many to many table).
Run the following command in terminal “orderSystem” folder path: “node server”
Also Install the “SQLite Viewer” extention in VS Code to view the sqlite db tables.
Additional Note: the port 3001 will be defined below step “
10. Connection of fronend & backend”.

// server
const express = require('express')
const db = require('./db')

const app = express()
const port = 3001

db.startDB()

app.use(express.json());

app.get('/api/items', async (req, res) => {
res.json(await db.getAllItems())
})
app.get('/api/orders/:id', async (req, res) => {
res.json(await db.getOrder(req.params.id))
})
app.post('/api/orders',async (req, res) => {
res.json(await db.createOrder(req.body))
})
app.use(express.static('frontend/dist'))

app.listen(port, () => {
console.log(`Testing and listening on port ${port}`)
})

FRONTEND ENVIRONMENT SETUP

6.1 Install Vuefity

Terminal command: “npm create vuetify” command
* 1) Then Type y ENTER
* 2) It wil ask you the name of the project. You can name it as “frontend
* 3) Then select “Essentials (Vuetify, VueRouter, Pinia)
* 4) Then Typescript say “No”
* 5) Then select “Npm”

And now it will create the “frontend” folder with all basic components ready.

6.2 Install Vee-Validate (Form Validation)

  • In vs code terminal open root project folder as an example: “orderSystem
  • Run this command in this folder then: npm install vee-validate

7. Run Frontend Server with Vuetify:

* 1) Go in “frontend” folder with this command in terminal “cd frontend
* 2) Now type and run this command “npm run dev” to run the frontend server

8. Use Vuetify Wireframe codes as a dashboard template:

Vuetify Constrained Wireframe Source Link Below:
https://github.com/vuetifyjs/vuetify/blob/master/packages/docs/src/examples/wireframes/constrained.vue

Get these sample codes from the link above and change them regarding to the following steps.

8.1. Layouts

* 8.1) Create the frontend > src > layouts > default > AppBar “ file with this codes like this below. (This is the nav bar menu items in header.)

// AppBar (top bar and menu items)
<template>
<v-app-bar flat>
<v-container class="mx-auto d-flex align-center justify-center">
<v-avatar
class="me-10 ms-4"
color="grey-darken-1"
size="32"
>
<img :src="avatarUrl" alt="Avatar">
</v-avatar>

<v-btn
v-for="link in links"
:key="link"
:to="link"
variant="text"
>
{{ link }}
</v-btn>

<v-spacer></v-spacer>

<v-responsive max-width="160">
<v-btn to="cart"> Shopping Cart ({{ store.countOfCartItems }}) </v-btn>
</v-responsive>
</v-container>
</v-app-bar>
</template>

<script setup>
import { useAppStore } from '@/stores/app';

//Urls after slash / like .../home or .../About (sayfa linki buradan belirlenir)
const links = [
// 'home',
'menu',
// 'location',
'about',
// 'cart',
]


// access the 'store'variable
const store = useAppStore()





const avatarUrl = 'https://t3.ftcdn.net/jpg/04/50/77/86/360_F_450778689_kRfr3ncRDnFusmWGuRSnN3UfPpHu2vET.jpg';
</script>

<style scoped>

.v-avatar {
border-radius: 10% !important;
}


.v-avatar >>> img {
width: 32px;
height: 32px;
object-fit: cover; /* Resmin kesilmeden sığması için */
}
</style>

Use now Vite & Vue Router for the AppBar.vue (Nav Menu):

Defining the “nav bar menu urls” in AppBar.vue file via “v-btn and its v-for loop feature”.
frontend > src > layouts > default > appBar.vue
!!!!! Important these page href urls has already been defined at the bottom of the page in “<script setup> const links = [ …. “ .
So the “links” in the v-btn loop are retrieved from this “<script setup> const links = [ ….” array
You can even edit the menu src urls from this “<script setup> const links = [ ….” array

<v-btn
v-for=”link in links
:key=”link”
:to=”link”
variant=”text”
>
{{ link }}
</v-btn>

<script setup>
const links = [ ‘Home’, ‘Menu’, ‘Location’, ‘About’, ]
</script>

* 8.2) Create the frontend > src > layouts > default > View “ file with this codes like this below.

// View (the component context will be displayed in here)
<template>
<v-main>
<router-view />
</v-main>
</template>
<script setup>
//
</script>

* 8.3) Create the frontend > src > layouts > default > Default “ file with this codes like this below.

// Default (this will contain  AppBar (topbar menu items etc.) and View (component content)
<template>
<v-app>
<default-bar />

<default-view />
</v-app>
</template>

<script setup>
import DefaultBar from './AppBar.vue'
import DefaultView from './View.vue'
</script>

8.2. Router

* 8.2.1) Create the frontend > src > router > index.js “ file with this codes like this below.

// Composables
import { createRouter, createWebHistory } from 'vue-router'


const routes = [
{
path: '/',
component: () => import('@/layouts/default/Default.vue'),
redirect: {
// it could be Home either
name: 'Menu'
},
children: [
{
path: 'home', // Ana dizin
name: 'Home',
component: () => import('@/views/Home.vue'),
},
{
path: 'menu', // Ana dizin
name: 'Menu',
component: () => import('@/views/Menu.vue'),
},
{
path: 'location', // Ana dizin
name: 'Location',
component: () => import('@/views/Location.vue'),
},
{
path: 'about', // Ana dizin
name: 'About',
component: () => import('@/views/About.vue'),
},
{
path: 'cart', // Ana dizin
name: 'Cart',
component: () => import('@/views/Cart.vue'),
},
{
path: 'checkout',
name: 'Checkout',
component: () => import('@/views/Checkout.vue'),
},
{
path: 'order/:id', // Ana dizin
name: 'order',
component: () => import('@/views/Order.vue'),
},
],
},
]


const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})

export default router

8.3. Components

The vueitfyjs could be used for the components.
https://vuetifyjs.com/en/components/sheets/

* 8.3.1) Create the frontend > src > components > MenuComp.vue “ file with this codes below.
Cards components could be used in this component.
https://vuetifyjs.com/en/components/cards/#usage

// Menu Page Content & Component

<template>


<v-row>
<!-- For loop for each item that retrieved from server.js -->
<v-col cols="3" v-for="i in store.menuItems ">


<v-card
:disabled="loading"
:loading="loading"
class="mx-auto my-12"
max-width="374"
>
<template v-slot:loader="{ isActive }">
<v-progress-linear
:active="isActive"
color="deep-purple"
height="4"
indeterminate
></v-progress-linear>
</template>

<!-- Dynamic Image For Loop Works :src="i.imageUrl" -->
<v-img
height="250"
:src="i.imageUrl"
cover
></v-img>

<v-card-item>
<!-- The name of each item that retrieved from server.js to the for loop of this template. -->
<v-card-title>{{ i.name }}</v-card-title>
</v-card-item>

<v-card-text>


<div class="my-4 text-subtitle-1">
$ {{ i.price }}
</div>

<div> {{ i.desc }}</div>
</v-card-text>

<v-card-actions>
<v-btn
color="deep-purple-lighten-2"
variant="text"
@click="addToCart(i)"
>
Add to cart
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>



</template>

<script setup>
import { useAppStore } from '@/stores/app'
import { onMounted } from 'vue';

// `useAppStore` fonksiyonunu çağırarak store'u alın
const store = useAppStore()

function addToCart(i) {
store.addToCart(i)
}
onMounted(async ()=>{
await store.syncMenuItems()
})
</script>

* 8.3.2) Create the frontend > src > components > AboutComp.vue “ file with this codes below.

// About Page Content & Component
<template>

<!-- https://vuetifyjs.com/en/components/sheets/ Buradan bakarak detayları öğrenebilirsin. -->
<v-sheet class="d-flex align-center" height="800">
<v-row>
<v-col cols="4" offset="1">
<v-img src="https://upload.wikimedia.org/wikipedia/commons/e/e4/Latte_and_dark_coffee.jpg"></v-img>
</v-col>
<v-col cols="6" class="ml-12 align-self-center">
<p class="text-h4 text-orange">Crafting Gourmet Delights for Your Enjoyment</p>
<p class="text-body-1 mt-4 pb-10">We are a proud subsidiary of Global Eats,
one of the world's leading culinary brands. Last year, we achieved sales of $20 million.
Our commitment to excellence is reflected in every dish we serve.
Join us and experience the perfect blend of flavors, innovation, and tradition.
At our core, we value quality, customer satisfaction, and culinary artistry,
ensuring an unforgettable dining experience for all our guests.</p>
</v-col>
</v-row>
</v-sheet>




</template>

<script setup>

</script>

<style scoped>

.v-col >>> .v-img {
border-radius: 10% !important;

}
</style>

* 8.3.3) Create the frontend > src > components > CartComp.vue “ file with this codes below.

// Cart / Sepet Page
<template>

<v-container class="mt-10">
<v-row v-for="i in store.cartItems">
<v-col>
<v-img :width="150" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRnboiTtVDjGOSOcrIUyfnfiKce2od9Gm6imA&s"></v-img>
</v-col>
<v-col class="mt-5">
<span class="text-h5 text-green">{{ i.item.name }}</span>
</v-col>
<v-col class="mt-3">
<span class="text-body-1 text-cyan-darken-4">{{ i.item.desc }}</span>
</v-col>
<v-col class="mt-2">
<span class="text-h6 text-orange">${{ i.item.price }}</span>
</v-col>
<v-col class="mt-2">
<v-btn icon="$minus" class="mr-4 mb-1" size="x-small" color="success" @click="store.decrement(i)"></v-btn>
<span class="text-h6">{{ i.amount }}</span>
<v-btn icon="$plus" class="ml-4 mb-1" size="x-small" color="success" @click="store.increment(i)"></v-btn>
</v-col>
<v-col class="mt-2"> <v-btn color="deep-purple-lighten-2" variant="flat"
@click="store.removeFromCart(i)">Delete</v-btn></v-col>
</v-row>
<v-row class="mx-auto">
<v-col cols="2" offset="9">
<v-btn color="red" variant="flat" block="" to="checkout">
Checkout
</v-btn>
</v-col>
</v-row>
</v-container>


</template>

<script setup>


import { useAppStore } from '@/stores/app'

// `useAppStore` fonksiyonunu çağırarak store'u alın
const store = useAppStore()

</script>

* 8.3.4) Create the frontend > src > components > CheckoutComp.vue “ file with this codes below.

// Checkout Page
<template>
<v-app id="inspire">
<v-main class="bg-grey-lighten-3">
<v-container>
<v-row>
<v-col>
<v-sheet min-height="70vh" rounded="lg">
<v-container>

<v-row no-gutters>
<v-col cols="5" offset="1">

<form @submit.prevent="submit">
<v-text-field v-model="name.value.value" :counter="10"
:error-messages="name.errorMessage.value" label="Name"></v-text-field>

<v-text-field v-model="phone.value.value" :counter="7"
:error-messages="phone.errorMessage.value"
label="Phone Number"></v-text-field>

<v-text-field v-model="email.value.value"
:error-messages="email.errorMessage.value"
label="E-mail"></v-text-field>

<v-btn class="mr-10" size="x-large" color="red" type="submit">
pay
</v-btn>

<v-btn @click="handleReset" size="x-large" color="grey">
clear
</v-btn>
</form>
</v-col>
<v-col cols="4" offset="1">
<v-row no-gutters>
<v-col cols="3">
<span class="text-h6">Item</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Unit Price</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Amount</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Price</span>
</v-col>
</v-row>

<v-row no-gutters v-for="i in store.cartItems" :key="n">
<v-col cols="3">
{{ i.item.name }}
</v-col>
<v-col cols="3">
${{ i.item.price }}
</v-col>
<v-col cols="3">
{{ i.amount }}
</v-col>
<v-col cols="3">
${{ i.item.price * i.amount }}
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="3" offset="6">
<span class="text-h6">Total: </span>
</v-col>
<v-col cols="3">
<span class="text-h6">${{ store.getTotalPrice }}</span>
</v-col>
</v-row>
</v-col>
</v-row>
</v-container>
</v-sheet>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>


<script setup>
import { useField, useForm } from 'vee-validate'
import { useAppStore } from '@/stores/app'
import router from '@/router'


// `useAppStore` fonksiyonunu çağırarak store'u alın
const store = useAppStore()


const { handleSubmit, handleReset } = useForm({
validationSchema: {
name(value) {
if (value?.length >= 2) return true

return 'Name needs to be at least 2 characters.'
},
phone(value) {
if (value?.length > 9 && /[0-9-]+/.test(value)) return true

return 'Phone number needs to be at least 9 digits.'
},
email(value) {
if (/^[a-z.-]+@[a-z.-]+\.[a-z]+$/i.test(value)) return true

return 'Must be a valid e-mail.'
},
},
})
const name = useField('name')
const phone = useField('phone')
const email = useField('email')
const submit = handleSubmit(async values => {
var request = {
email: values.email,
name: values.name,
phone: values.phone,
items: store.cartItems
}
const order = await store.submitOrder(request)
console.log(order)
router.push({ path: `/order/${order.id}` })
})
</script>

* 8.3.5) Create the frontend > src > components > OrderComp.vue “ file with this codes below.

// Order Page
<template>
<v-app id="inspire">
<v-main class="bg-grey-lighten-3">
<v-container v-if="order">
<v-sheet min-height="70vh" rounded="lg">
<v-container>
<v-row no-gutters>
<v-col cols="4" offset="1">
<v-row no-gutters>
<v-col cols="6">
<span class="text-h6">Order Number: </span>
</v-col>
<v-col cols="6">
<span class="text-h6"> {{ order.id }}</span>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="6">
<span class="text-h6">Date: </span>
</v-col>
<v-col cols="6">
<span class="text-h6"> {{ new Date(order.date).toLocaleDateString() }}</span>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="6">
<span class="text-h6">Name: </span>
</v-col>
<v-col cols="6">
<span class="text-h6"> {{ order.name }}</span>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="6">
<span class="text-h6">Email: </span>
</v-col>
<v-col cols="6">
<span class="text-h6"> {{ order.email }}</span>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="6">
<span class="text-h6">Phone: </span>
</v-col>
<v-col cols="6">
<span class="text-h6"> {{ order.phone }}</span>
</v-col>
</v-row>
</v-col>
<v-col cols="4" offset="1">
<v-row no-gutters>
<v-col cols="3">
<span class="text-h6">Item</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Unit Price</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Amount</span>
</v-col>
<v-col cols="3">
<span class="text-h6">Price</span>
</v-col>
</v-row>
<v-row no-gutters v-for="item in order.Items" :key="id">
<v-col cols="3">
{{ item.name }}
</v-col>
<v-col cols="3">
${{ item.OrderItem.price }}
</v-col>
<v-col cols="3">
{{ item.OrderItem.amount }}
</v-col>
<v-col cols="3">
${{ item.OrderItem.price * item.OrderItem.amount }}
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="3" offset="6">
<span class="text-h6">Total: </span>
</v-col>
<v-col cols="3">
<span class="text-h6">${{ order.price }}</span>
</v-col>
</v-row>
</v-col>
</v-row>
</v-container>
</v-sheet>
</v-container>
</v-main>
</v-app>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useAppStore } from '@/stores/app'
const props = defineProps({
id: Number
})

const store = useAppStore()
const order = ref()
onMounted(async () => {
order.value = await store.getOrder(props.id)
})
</script>

8.4. Views

* 8.4.1 ) Create the frontend > src > views > Menu.vue“ file with this codes below.

// Menu Page View
<template>
<MenuComp />
</template>

<script setup>
import MenuComp from "@/components/MenuComp.vue"
</script>

* 8.4.2 ) Create the frontend > src > views > About.vue“ file with this codes below.

// About Page View
<template>
<AboutComp />
</template>

<script setup>
import AboutComp from "@/components/AboutComp.vue"
</script>

* 8.4.3 ) Create the frontend > src > views > Cart.vue“ file with this codes below.

// Cart Page View
<template>
<CartComp />
</template>

<script setup>
import CartComp from "@/components/CartComp.vue"
</script>

* 8.4.4 ) Create the frontend > src > views > Checkout.vue“ file with this codes below.

// Checkout Page View
<template>
<CheckoutComp />
</template>

<script setup>
import CheckoutComp from "@/components/CheckoutComp.vue"
</script>

* 8.4.5 ) Create the frontend > src > views > Order.vue“ file with this codes below.

// Order Page View
<template>
<OrderComp :id="$route.params.id"/>
</template>

<script setup>
import OrderComp from '@/components/OrderComp.vue'
</script>

8.5 Plugins

* 8.5.1) Create the frontend > src > plugins > index.js “ file with this codes below.

/**
* plugins/index.js
*
* Automatically included in `./src/main.js`
*/

// Plugins
import vuetify from './vuetify'
import pinia from '@/stores'
import router from '@/router'

export function registerPlugins (app) {
app
.use(vuetify)
.use(router)
.use(pinia)
}

* 8.5.1) Create the frontend > src > plugins > vuetify.js “ file with this codes below.

/**
* plugins/vuetify.js
*
* Framework documentation: https://vuetifyjs.com`
*/

// Styles
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'

// Composables
import { createVuetify } from 'vuetify'

// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'dark',
},
})

8.6. App.vue

* 8.6.1) Create the frontend > src > App.vue “ file with this codes below.

// App.vue 
<template>
<router-view />
</template>

<script setup>
//
</script>

8.7. Main.js

* 8.7.1) Create the frontend > src > App.vue “ file with this codes below.

// Main.js 

// Plugins
import { registerPlugins } from '@/plugins'

// Components
import App from './App.vue'

// Composables
import { createApp } from 'vue'

const app = createApp(App)

registerPlugins(app)

app.mount('#app')

9. Using Pinia to manage data in frontend

Now we are going to use pinia library (already installed during the step 6. Vuetify setup) to use it as an api in this web app.

Therefore, frontend > src > stores > app.js has already exists.

* 9.1) To begin with, go to https://pinia.vuejs.org/core-concepts/#Option-Stores and copy the following code and paste it in frontend > src > stores > app.js


// App.js
// Utilities
import { defineStore } from 'pinia'

export const useAppStore = defineStore('app', {
state: () => ({
menuItems: [], cartItems: [],
}),
getters: {
countOfCartItems: (state) => state.cartItems.length,
getTotalPrice: (state)=> {
let total = 0
state.cartItems.map(c=>{
total += (c.amount * c.item.price)
})
return total
}
},
actions: {

async syncMenuItems() {
const response = await fetch('/api/items')
this.menuItems = await response.json()
},


async submitOrder(request) {
const requestOptions = {
method: "POST",
headers: {"Content-Type": "application/json" },
body: JSON.stringify(request)
};
const response = await fetch("/api/orders", requestOptions)
this.cartItems = []
return await response.json()
},

async getOrder(id) {
const response = await fetch(`/api/orders/${id}`);
return response.json();
},


addToCart(item) {
let found = false;
for(let ci of this.cartItems){
if(ci.item.id === item.id) {
ci.amount++
found = true
break;
}
}
console.log(item)
console.log(found)
if(!found) {
this.cartItems.push({
item: item,
amount: 1
})
}
// calc totalvalue

},
removeFromCart(item) {
this.cartItems = this.cartItems.filter(i=>i!=item)
},
increment(item) {
item.amount++
},
decrement(item) {
item.amount--
}
},
})

* 9.2) Secondly, create and copy the following code and paste it in frontend > src > stores > index.js
It will feed the frontend.

// index.js
// Utilities
import { createPinia } from 'pinia'

export default createPinia()

10. Connection of fronend & backend

First we need to make apis work in frontend port to be able to test it easily.

To do that we need to have test.http file and server.js file (we defined it in step 5)

So lets start change target of server > proxy > api in vite.config.mjs file.

orderSystem > Frontend > Vite.config.mjs file.

server: {
port: 3000,
proxy: {
'/api' : {
target: 'http://localhost:3001'
}
}
},

orderSystem > Test.http

GET http://localhost:3000/api/items

###
GET http://localhost:3001/api/orders/1

###
POST http://localhost:3001/api/orders
content-type: application/json

{
"name": "foo bar",
"email": "abc@ef.com",
"phone": "1233321322",
"items":[{
"item":{
"id": 1,
"price": 12
},
"amount": 2
}
]
}

So that we will be able to use fetch url like this below without full url

orderSystem > frontend > src > stores > app.js

Instead of this =>

actions: {

async syncMenuItems() {
const response = await fetch('http://localhost:3001/api/items')
this.menuItems = await response.json()
},

We’ll use it like this =>

actions: {

async syncMenuItems() {
const response = await fetch('/api/items')
this.menuItems = await response.json()
},

11. Running and testing

* 11.1) Open terminal and go to “orderSystem” project folder path then run this code below first for running backend db and data and api
node server

* 11.2) Then go to frontend folder and run this code below for running frontend vue
npm run dev

Now it should be run like this. If it is congratilation you have create an ordersystem app successfully.

--

--

Mert Kadir Gursoy
Mert Kadir Gursoy

Written by Mert Kadir Gursoy

Product Team Lead | Product Management ~ Product Design ~ UX Research ~ Technical Project Management

No responses yet