Welcome to our concise tutorial on building a Cafe Customer Relationship Management (CRM) system using the MERN stack. This step-by-step guide will take you through the entire process, from database design to frontend development, enabling you to create a customized CRM for your Cafe. Whether you’re a developer seeking to expand your skills or a Cafe owner wanting to optimize customer experiences, this tutorial provides a fast track to success.
Join us on this journey, and in no time, you’ll be equipped to elevate your Cafe management with a tailored MERN-based CRM. Let’s begin!
Planning before executing your goal is not just a wise choice; it’s the foundation of success. When you plan, you chart a clear path forward, set achievable milestones, and anticipate potential roadblocks. Before mindlessly executing our CRM let’s first discuss the plan of execution.
✅ ERD: It defines the architecture of our database
✅ MongoDB Atlas: Database host
✅ Node.js: Backend server
✅ Express.js: Backend Server
✅ Frontend: Next.js
✅ Design: Tailwind
➡️ Body parser: Parse JSON body from the client
➡️ Cors: Handle cors
➡️ Express: Node.js
➡️ Mongoose: ORM for MongoDB
"dependencies": { "body-parser": "^1.20.2", "cors": "^2.8.5", "express": "^4.18.2", "mongoose": "^7.4.5" }
Step 1
Create index.js file.
Import express and initialize the application.
const express = require('express') const app = express()
Step 2
Install dependencies and import the dependencies.
const bodyParser = require('body-parser') const cors = require('cors') app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.use(cors()) const { config } = require('./config') const { PORT } = config app.listen(PORT, () => { console.log(`App listening on port ${PORT}`) })
Step 3
To create a database connection, we are using MongoDB Atlas as our database provider.
// models/index.js const mongoose = require('mongoose'); const fs = require('fs'); const path = require('path'); const { config } = require('../config') const { MONGO_URI } = config const mongoOptions = { useNewUrlParser: true, useUnifiedTopology: true } const connect = () => mongoose.connect(MONGO_URI, mongoOptions); connect().then(()=>{ //You may use loggers to handle log console.log('Connection success') }) .catch((e)=>{ console.log('error----------',e) })
Step 4
After creating a connection let’s start creating models with which we are going to query the database.
const mongoose = require('mongoose'); const CategorySchema = new mongoose.Schema({ name: String, status:{ type: String, enum: ['active','inactive'], default: 'active' } }); const Category = mongoose.model('Category', CategorySchema); module.exports = Category;
Step 5
Now we have our models configured let’s create routes to handle.
const express = require('express') const app = express() const bodyParser = require('body-parser') const cors = require('cors') app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.use(cors()) require('./models') const { config } = require('./config') const { PORT } = config const product = require('./routes/product') const customer = require('./routes/customer') const category = require('./routes/category') app.use('/product', product) app.use('/customer', customer) app.use('/category', category) app.listen(PORT, () => { console.log(`App listening on port ${PORT}`) })
Step 6
Now let’s create a routes file and write code to handle the request.
let express = require('express') const router = express.Router() const { getCategories } = require('../controller/Category') router.get('/', async (req, res, next) => { try { let response = await getCategories() res.json({ success: true, data: response, message: 'Categories Fetched successfully' }) } catch (error) { console.log("🚀 ~ file: category.js:10 ~ router.get ~ error:", error) } })
Now let’s encapsulate our business logic into a controller file:
const Category = require('../models/Category') /** * API to fetch categories * @returns */ const getCategories = () => { return Category.find({}) .then((data) => { return data }) .catch((e) => { throw new Error(e) }) }
Likewise, we can create functions for other operations as well like Add, Edit, Get Category by ID
/** * API to fetch category by Id * @returns */ const getCategoryById = () => { Category.findById(req.params.id) .then((data) => { return data }) .catch((e) => { throw new Error(e) }) } /** * API to add categories * @returns */ const addCategory = (data) => { Category.create(data) .then((data) => { return data }) .catch((e) => { throw new Error(e) }) } /** * API to update categories * @returns */ const updateCategory = (id,data) => { Category.findByIdAndUpdate(id, data) .then(()=>{ return true }) .catch((e) => { throw new Error(e) }) } module.exports = { getCategories, getCategoryById, addCategory, updateCategory }
If you notice we have not used the Delete operation instead we will use the status key to manage the data.
Finally, the routes file will look like this:
let express = require('express') const router = express.Router() const { getCategories, getCategoryById, addCategory, updateCategory } = require('../controller/Category') router.get('/', async (req, res, next) => { try { let response = await getCategories() res.json({ success: true, data: response, message: 'Categories Fetched successfully' }) } catch (error) { console.log("🚀 ~ file: category.js:10 ~ router.get ~ error:", error) } }) router.get('/:id', async (req, res) => { try { let response = await getCategoryById(req.params.id) res.json({ success: true, data: response, message: 'Category Fetched successfully' }) } catch (error) { console.log("🚀 ~ file: category.js:10 ~ router.get ~ error:", error) } }) router.post('/', async (req, res) => { try { let data = req.body let response = await addCategory(data) res.json({ success: true, data: response, message: 'Category Added successfully' }) } catch (error) { console.log("🚀 ~ file: category.js:10 ~ router.get ~ error:", error) } }) router.put('/:id', async (req, res) => { try { let data = req.body let id = req.params.id let response = await updateCategory(id, data) res.json({ success: true, message: 'Category updated successfully', data: {} }) } catch (error) { console.log("🚀 ~ file: category.js:58 ~ router.get ~ error:", error) } }) module.exports = router
Now following the same framework we will create the route handler and controller for Product and Customer.
Reference: GitHub-Cafe-Backend
After creating the backend server we will now proceed with our frontend practice to make the user interact with the API.
➡️ Framework: Next.js
➡️ UI: Tailwind CSS
➡️ Components: Flowbite
➡️ Form handling : Formik
➡️ Validation: Yup
npx create-next-app@latest
During installation, you’ll be asked for a certain configuration, for initial setup you can go with default settings.
In the app root folder, under layout, we are using the following configuration. I’ve used custom styling and components. You can use any configuration you feel fit and it’s absolutely okay if the file looks different. We are focusing on the concepts. Make sure you focus on the concept and approach.
import './globals.css' import type { Metadata } from 'next' import { Inter } from 'next/font/google' import { Sidebar } from './components/sidebar' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Cafe CRM Frontend', description: 'CRM to handle Cafe orders', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}> <main className="flex min-h-screen"> <Sidebar /> <div className="w-full"> {children} </div> </main> </body> </html> ) }
As per the documentation layout.tsx folder is the bootstrap file for our application. If you notice it has children’s prop. All the child components will be rendered here.
I’ve used some basic HTML and Tailwind configuration to create a simple admin dashboard layout. For code readability and scalability, I’ve created reusable components with dedicated names within the app root folder.
Related read: What is Code Restructuring?
Rest all the folders within the app root folder are default routing.
Now to interact with the APIs we will be creating a file that handles our API interactions.
app/api.ts. I’ve utilized fetch APIs for API calls you can use Axios or any library you are comfortable with.
Reference: GitHub-Cafe-Frontend
After completing the installation and initial setup, let’s focus on the performing operations for the category element.
Route: /category
File: app/category/page.tsx
Imports-
Category: Render list component (app/components/category.tsx)
getCategory: Function to get the list of categories
To maintain the readability and reusability we have encapsulated the table to app/components/category.tsx file and using import we call this component to render the list.
Fetching the data-
We will be importing our getCategory() function from app/api.tsx and calling the function.
After getting the data from the API call we will provide the data to the list render component.
The final code will look like this:
import React from "react" import { Category } from "../components/category" //importing render list import { getCategory } from "../apis" // Calling the function from api async function fetchData() { const categories = await getCategory() // making the api call return { categories } } const categoryPage = async () => { let { categories } = await fetchData() return ( <> <div className="body h-full"> <Category data={categories} />// passing props to the list component </div> </> ) } export default categoryPage
Route: /category/add
File: app/category/add/[..id]/page.tsx
Imports-
CategoryForm: Component containing Form handling logic (app/components/category.tsx)
useParams: To read url params
getCategoryById: Function to get the category details (app/apis.tsx)
To handle the route to add the data to a dedicated element, I’ve added the add folder category/add/[..id]/page.tsx. If you notice that we have added a slug, I’ll tell you more about the slug in the update part.
"use client" import Link from "next/link" import React, { useState, useEffect } from "react" import { CategoryForm } from "@/app/forms/category" import { useParams } from 'next/navigation' import { getCategoryById } from "@/app/apis" const CategoryPage = () => { const params = useParams() const { id } = params const [category, setCategory] = useState({}); const [isEdit, setIsEdit] = useState('') useEffect(() => { isEditFunction() }, []) const isEditFunction = async () => { try { if (id) { const categoryDetail = await getCategoryById(id) setIsEdit(id[0]) setCategory(categoryDetail.data) } } catch (error) { console.log("🚀 ~ file: page.tsx:21 ~ isEditFunction ~ error:", error) } } return ( <div className="w-full h-full"> <div className="bg-white shadow-md rounded-lg px-3 py-2 mb-4"> <div className="flex justify-between align-center text-gray-700 text-lg font-semibold py-2 px-2"> <p>{isEdit ? "Edit Category" : "Add Category"}</p> <Link href='/category' className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Back </Link> </div> <div className="w-full"> <CategoryForm isEdit={isEdit} category={category} /> </div> </div> </div > ) } export default CategoryPage
Now when we click on the add category we will be redirected to the form page to add the category.
Now in the app/forms/category.tsx. For form handling, we are using Formik and Yup to validate forms.
After binding the Formik and Yup with our form we will have a code that looks like the below:
"use client" import { Field, Form, Formik } from 'formik'; import * as Yup from 'yup'; import { addCategory, editCategory } from '../apis' import { useRouter } from 'next/navigation' import { useEffect, useState, useRef } from 'react'; export const CategoryForm = ({ isEdit, category }) => { const router = useRouter() const formRef = useRef() const [form, setForm] = useState({ name: '' }) useEffect(() => { checkIsEdit() }, [isEdit]) const checkIsEdit = () => { if (isEdit) { formRef.current.setFieldValue('name', category.name) } } const CategorySchema = Yup.object().shape({ name: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Required') }); const handleFormSubmit = async (formData: Object) => { try { let response = await !isEdit ? addCategory(formData) : editCategory(formData, isEdit) router.push('/category') } catch (error) { console.log("🚀 ~ file: category.tsx:15 ~ handleFormSubmit ~ error:", error) } } return ( <div className="mb-6"> <Formik innerRef={formRef} initialValues={form} validationSchema={CategorySchema} onSubmit={handleFormSubmit} > {({ values, errors, touched, handleSubmit, isSubmitting, }) => ( <Form onSubmit={handleSubmit}> {errors.name && touched.name && errors.name} <Field className="bg-gray-50 m-2 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter Category" type="text" name="name" /> <button className="bg-blue-500 m-2 hover:bg-blue-700 text-white font-bold py-2 px-4 mt-3 rounded" type="submit" disabled={isSubmitting}> {isEdit ? 'Update' : 'Add'} </button> </Form> )} </Formik> </div> ) }
Route: /category/add/:categoryId
File: app/category/add/[..id]/page.tsx
Imports-
CategoryForm: Component containing Form handling logic (app/components/category.tsx)
useParams: To read url params
getCategoryById: Function to get the category details (app/apis.tsx)
If you notice in our add form we are passing a slug, with the help of isEditFunction() we can check if the request is to add the category or to edit. So when we pass the optional slug parameter to the category/add route the system will automatically perform the update operation.
While updating a form it’s crucial to re-populate the fields.isEditFunction() provides us with the data. Now after having the data, we will utilize Formik API to bind the value to the field.
formRef.current.setFieldValue('name', category.name)
This will help us bind the value to the “name” field, and this is what the final render will look like when we click to update the category.
We have integrated the APIs to interact with category elements. Using the same logic we can create API integrations for products and customers as well.
In this comprehensive tutorial, we crafted a Cafe CRM with the dynamic MERN stack, whether you’re a developer or Cafe owner. We tackled database design and frontend development, delving into backend setup, model creation, and route building.
With a user-friendly interface using Next.js, Tailwind CSS, Formik, and Yup, we explored routing and dynamic category editing. This empowers you to manage Cafe data efficiently and expand your CRM for a tailored solution. Cheers to your Cafe CRM journey, whether brewing coffee or code!
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowMindbowser played a crucial role in helping us bring everything together into a unified, cohesive product. Their commitment to industry-standard coding practices made an enormous difference, allowing developers to seamlessly transition in and out of the project without any confusion....
CEO, MarketsAI
I'm thrilled to be partnering with Mindbowser on our journey with TravelRite. The collaboration has been exceptional, and I’m truly grateful for the dedication and expertise the team has brought to the development process. Their commitment to our mission is...
Founder & CEO, TravelRite
The Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork