Frameworks used:
MongoDB
Express JS
Node JS
React JS
Library used:
Features:
User can create account.
User can log back into their account.
User can view account balance.
User can search for other users.
User can make payment to other users.
User can logout of their accounts.
Payments App:
Try: https://gorgeous-rolypoly-efa90b.netlify.app/
Github Link:
https://github.com/maniksharma17/payment-app-basic
Database
I used MongoDB for database service. I imported mongoose library for an easy MongoDB connection. With mongoose, I defined two schemas and created models with them:
const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true, trim: true, lowercase: true, minLength: 3, }, firstName: { type: String, required: true, maxLength: 20 }, lastName: { type: String, required: true, maxLength: 20 }, password: { type: String, minLength: 6, required: true, }, }) const accountsSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, balance: { type: Number, required: true } }) userSchema.pre('save', function(next){ this.firstName = this.firstName.charAt(0).toUpperCase() + this.firstName.slice(1).toLowerCase() this.lastName = this.lastName.charAt(0).toUpperCase() + this.lastName.slice(1).toLowerCase() next() }) const User = mongoose.model("User", userSchema) const Account = mongoose.model("Account", accountsSchema) module.exports = { User, Account }
Routing
I used express for routing through different HTTP methods. I created the main route, and sent all requests to /backend/routes/index using Router.express(). Remember to export the Router as well, otherwise express will throw an error.
This is how main index file looks:
const express = require("express"); const cors = require("cors") const app = express() require('dotenv').config(); const PORT = process.env.PORT app.use(cors()) app.use(express.json()) const rootRouter = require("./routes/index") app.use("/api/v1", rootRouter) app.listen(PORT, ()=>{ console.log("Listening on PORT 3000") })
From routes/index, I sent user requests to /routes/user and account requests to /routes/account. How routes/index looks:
const express = require("express") const router = express.Router() router.use(express.json()) const userRouter = require("./user") router.use("/user", userRouter) const accountRouter = require("./account") router.use("/account", accountRouter) module.exports = router
routes/user
redirected from routes/index
/signup and /signin (POST)
router.post("/signup", async (req, res)=>{ const userData = req.body const parsedUserData = signupSchema.safeParse(userData) if (parsedUserData.success){ const existingUser = await User.findOne({username: userData.username}) if (existingUser){ res.status(200).json({message: "Username already exixts.", error: true}) } else{ User.create({ username: userData.username, password: userData.password, firstName: userData.firstName, lastName: userData.lastName }).then((user)=>{ const userID = user._id const token = jwt.sign({userID}, jwtkey) Account.create({ userId: user._id, balance: Math.floor(Math.random() * 10000) }) res.status(200).json({ message: "User created successfully", token: token }) return }) } } else { res.json({parsedUserData, parseError: true}) return } }) router.post("/signin", async (req, res)=>{ const userData = req.body if (!(signinSchema.safeParse(userData).success)){ res.status(411).json({ message: "Invalid username / password", error: true }) return } const existingUser = await User.findOne({username: userData.username, password: userData.password}) if (existingUser){ const token = jwt.sign({userID: existingUser._id}, jwtkey) res.status(200).json({ token: token, firstName: existingUser.firstName }) } else{ res.status(411).json({ message: "Username / password does not exist.", error: true }) } })
/user/bulk (GET)
Route to get all users as per search filter.
router.get("/bulk", authMiddleware, (req, res)=>{ const filter = req.query.filter || ""; User.find({ $or: [ {firstName: { "$regex": filter }}, {lastName: { "$regex": filter}} ]}) .then((users)=>{ res.json({ users: users.map(user => ({ username: user.username, firstName: user.firstName, lastName: user.lastName, _id: user._id })) }) }) })
/getUser (GET)
Route to get a particular user on button click (for payment)
router.get("/getUser", authMiddleware, async (req, res) => { const userid = req.query.id const user = await User.findOne({_id: userid}) res.json({user}) })
routes/account
redirected from routes/index
/balance (GET)
router.get("/balance", async (req, res)=>{ const account = await Account.findOne({userId: req.userID}) res.status(200).json({balance: account.balance}) })
/transfer (POST)
I used session() and startTransaction()/commitTransaction() here. It is used when multiple related database calls have to be executed.
router.post("/transfer", async (req, res)=>{ const session = await mongoose.startSession() session.startTransaction() const fromAccount = req.userID const toAccount = req.body.to const amount = req.body.amount const toAccountVerification = await Account.findOne({userId: toAccount}).session(session) if (!toAccountVerification){ await session.abortTransaction() res.status(400).json({message: "Invalid account"}) } const user = await Account.findOne({userId: fromAccount}).session(session) if (user.balance < amount){ await session.abortTransaction() res.status(400).json({ message: "Insufficient balance" }) return } await Account.updateOne({userId: fromAccount}, { "$inc": { balance: -amount } }).session(session) await Account.updateOne({userId: toAccount}, { "$inc": { balance: amount } }).session(session) await session.commitTransaction() res.status(200).json({message: "Transaction successful"}) })
Authentication and Authorization
For authentication, I used JWT (jsonwebtoken) and for Authorization, I imported the Zod library. A jwt token is generated every time a user signs into their account. That token is sent as a header with every network request for verification of the user. I created a middleware in express for verifying the jwt token.
const jwtkey = process.env.JWT_SECRET_KEY const jwt = require("jsonwebtoken") require('dotenv').config(); function authMiddleware(req, res, next){ const header = req.headers.authorization if (!header || !header.startsWith('Bearer ')){ res.json({}) } token = header.split(" ")[1] try { const verifiedJWT = jwt.verify(token, jwtkey) req.userID = verifiedJWT.userID next() } catch(err) { res.json({}) } } module.exports = { authMiddleware }
ZOD Schemas
const z = require("zod") const signupSchema = z.object({ username: z.string().email().min(3, {message: "Username must be greater than 3 characters."}).max(30, {message: "Username must be less than 30 characters."}).trim().toLowerCase(), firstName: z.string().max(20, {message: "First name should be less than 20 characters."}), lastName: z.string().max(20, {message: "First name should be less than 20 characters."}), password: z.string().min(6, {message: "Password must be atleast 6 characters long."}), }) const signinSchema = z.object({ username: z.string().email().min(3, {message: "Username must be greater than 3 characters."}).max(30, {message: "Username must be less than 30 characters."}).trim().toLowerCase(), password: z.string().min(6, {message: "Password must be atleast 6 characters long."}), }) const updateSchema = z.object({ password: z.string().min(6, {message: "Password must be atleast 6 characters long."}).optional(), firstName: z.string().max(20, {message: "First name should be less than 20 characters."}).optional(), lastName: z.string().max(20, {message: "First name should be less than 20 characters."}).optional(), })
FRONTEND
For changing views, I used react-router-dom, and for state management, I used Recoil. You can style the website using Tailwind CSS.
import { Suspense } from "react" import Dashboard from "./assets/components/Dashboard" import Signin from "./assets/components/Signin" import Signup from "./assets/components/Signup" import { BrowserRouter, Route, Routes } from "react-router-dom" import { RecoilRoot } from "recoil" function App() { return ( <BrowserRouter> <RecoilRoot> <Routes> <Route path="/" element={<Suspense fallback={"Loading..."}><Signin></Signin></Suspense>}></Route> <Route path="/signup" element={<Signup></Signup>}></Route> <Route path="/dashboard" element={<Suspense fallback={"Loading..."}><Dashboard></Dashboard></Suspense>}></Route> </Routes> </RecoilRoot> </BrowserRouter> ) } export default App
Thank you for Reading!
评论
发表评论