DailyCode[1]
DailyCode[1]
We’re building a PayTM like application that let’s users send money to each other given an
initial dummy balance
Things to do
Clone the 8.2 repository from https://github.com/100xdevs-cohort-2/paytm
Copy
git clone https://github.com/100xdevs-cohort-2/paytm
💡 Please keep a MongoDB url handy before you proceed. This will be your primary
database for this assignment
1. Create a free one here - https://www.mongodb.com/
2. There is a Dockerfile in the codebase, you can run mongo locally using it.
Backend
1. Express - HTTP Server
Copy
const express = require("express");
index.js
Frontend
1. React - Frontend framework
Copy
function App() {
return (
<div>
Hello world
</div>
)
}
App.jsx
To start off, create the mongo schema for the users table
Solution
Simple solution
Copy
// backend/db.js
const mongoose = require('mongoose');
module.exports = {
User
};
Elegant Solution
Copy
// backend/db.js
const mongoose = require('mongoose');
module.exports = {
User
};
Solution
Copy
// backend/api/index.js
const express = require('express');
module.exports = router;
Step 2
Import the router in index.js and route all requests from /api/v1 to it
Solution
Copy
// backend/index.js
const express = require("express");
const rootRouter = require("./routes/index");
app.use("/api/v1", rootRouter);
Step 4 - Route user requests
Solution
Copy
// backend/routes/user.js
const express = require('express');
module.exports = router;
Solution
Copy
// backend/routes/index.js
const express = require('express');
const userRouter = require("./user");
router.use("/user", userRouter)
module.exports = router;
Step 5 - Add cors, body parser and
jsonwebtoken
1. Add cors
Since our frontend and backend will be hosted on separate routes, add the cors
middleware to backend/index.js
Hint
Look at https://www.npmjs.com/package/cors
Solution
Copy
// backend/index.js
const express = require('express');
const cors = require("cors");
app.use(cors());
module.exports = router;
2. Add body-parser
Since we have to support the JSON body in post requests, add the express body parser
middleware to backend/index.js
You can use the body-parser npm library, or use express.json
Hint
https://medium.com/@mmajdanski/express-body-parser-and-why-may-not-need-it-
335803cd048c
Solution
Copy
// backend/index.js
const express = require('express');
const cors = require("cors");
const rootRouter = require("./routes/index");
const app = express();
app.use(cors());
app.use(express.json());
app.use("/api/v1", rootRouter);
3. Add jsonwebtoken
Copy
npm install jsonwebtoken
4. Export JWT_SECRET
Export a JWT_SECRET from a new file backend/config.js
Solution
Copy
//backend/config.js
module.exports = {
JWT_SECRET: "your-jwt-secret"
}
Solution
Copy
// backend/index.js
... Existing code
app.listen(3000);
Step 6 - Add backend auth routes
1. Signup
This route needs to get user information, do input validation using zod and store the
information in the database provided
If all goes well, we need to return the user a jwt which has their user id encoded as follows -
Copy
{
userId: "userId of newly added user"
}
💡 Note - We are not hashing passwords before putting them in the database. This is
standard practise that should be done, you can find more details here -
https://mojoauth.com/blog/hashing-passwords-in-nodejs/
Method: POST
Route: /api/v1/user/signup
Body:
Copy
{
username: "[email protected]",
firstName: "name",
lastName: "name",
password: "123456"
}
Response:
Status code - 200
Copy
{
message: "User created successfully",
token: "jwt"
}
Copy
{
message: "Email already taken / Incorrect inputs"
}
Solution
Copy
const zod = require("zod");
const { User } = require("../db");
const jwt = require("jsonwebtoken");
const { JWT_SECRET } = require("../config");
if (existingUser) {
return res.status(411).json({
message: "Email already taken/Incorrect inputs"
})
}
res.json({
message: "User created successfully",
token: token
})
})
2. Route to sign in
Let’s an existing user sign in to get back a token.
Method: POST
Route: /api/v1/user/signin
Body:
Copy
{
username: "[email protected]",
password: "123456"
}
Response:
Status code - 200
Copy
{
token: "jwt"
}
Copy
{
message: "Error while logging in"
}
Solution
Copy
if (user) {
const token = jwt.sign({
userId: user._id
}, JWT_SECRET);
res.json({
token: token
})
return;
}
res.status(411).json({
message: "Error while logging in"
})
})
Copy
// backend/routes/user.js
const express = require('express');
if (existingUser) {
return res.status(411).json({
message: "Email already taken/Incorrect inputs"
})
}
res.json({
message: "User created successfully",
token: token
})
})
if (user) {
const token = jwt.sign({
userId: user._id
}, JWT_SECRET);
res.json({
token: token
})
return;
}
res.status(411).json({
message: "Error while logging in"
})
})
module.exports = router;
Step 7 - Middleware
Now that we have a user account, we need to gate routes which authenticated users can
hit.
3. Puts the userId in the request object if the token checks out.
Copy
Header -
Authorization: Bearer <actual token>
Solution
Copy
const { JWT_SECRET } = require("./config");
const jwt = require("jsonwebtoken");
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.userId = decoded.userId;
next();
} catch (err) {
return res.status(403).json({});
}
};
module.exports = {
authMiddleware
}
1. password
2. firstName
3. lastName
Whatever they send, we need to update it in the database for the user.
Use the middleware we defined in the last section to authenticate the user
Method: PUT
Route: /api/v1/user
Body:
Copy
{
password: "new_password",
firstName: "updated_first_name",
lastName: "updated_first_name",
}
Response:
Status code - 200
Copy
{
message: "Updated successfully"
}
Copy
{
message: "Error while updating information"
}
Solution
Copy
const { authMiddleware } = require("../middleware");
res.json({
message: "Updated successfully"
})
})
2. Route to get users from the backend, filterable via
firstName/lastName
This is needed so users can search for their friends and send them money
Method: GET
Route: /api/v1/user/bulk
Query Parameter: ?filter=harkirat
Response:
Status code - 200
Copy
{
users: [{
firstName: "",
lastName: "",
_id: "id of the user"
}]
}
Hints
https://stackoverflow.com/questions/7382207/mongooses-find-method-with-or-
condition-does-not-work-properly
https://stackoverflow.com/questions/3305561/how-to-query-mongodb-with-like
Solution
Copy
router.get("/bulk", async (req, res) => {
const filter = req.query.filter || "";
res.json({
user: users.map(user => ({
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
_id: user._id
}))
})
})
Accounts table
The Accounts table will store the INR balances of a user.
The schema should look something like this -
Copy
{
userId: ObjectId (or string),
balance: float/number
}
Copy
In the real world, you shouldn’t store `floats` for balances in the databas
You usually store an integer which represents the INR value with
decimal places (for eg, if someone has 33.33 rs in their account,
you store 3333 in the database).
There is a certain precision that you need to support (which for india is
2/4 decimal places) and this allows you to get rid of precision
errors by storing integers in your DB
Copy
const accountSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId, // Reference to User model
ref: 'User',
required: true
},
balance: {
type: Number,
required: true
}
});
module.exports = {
Account
}
Copy
// backend/db.js
const mongoose = require('mongoose');
mongoose.connect("mongodb://localhost:27017/paytm")
module.exports = {
User,
Account,
};
Step 10 - Transactions in databases
Copy
const mongoose = require('mongoose');
const Account = require('./path-to-your-account-model');
// Example usage
transferFunds('fromAccountID', 'toAccountID', 100);
Answer
1. What if the database crashes right after the first request (only the balance is
decreased for one user, and not for the second user)
It would lead to a database inconsistency . Amount would get debited from the first
user, and not credited into the other users account.
This is so we don’t have to integrate with banks and give them random balances to start
with.
Solution
Copy
router.post("/signup", async (req, res) => {
const { success } = signupBody.safeParse(req.body)
if (!success) {
return res.status(411).json({
message: "Email already taken / Incorrect inputs"
})
}
if (existingUser) {
return res.status(411).json({
message: "Email already taken/Incorrect inputs"
})
}
await Account.create({
userId,
balance: 1 + Math.random() * 10000
})
res.json({
message: "User created successfully",
token: token
})
})
All user balances should go to a different express router (that handles all requests on
/api/v1/account ).
Solution
Copy
// backend/routes/account.js
const express = require('express');
const router = express.Router();
module.exports = router;
2. Route requests to it
Send all requests from /api/v1/account/* in routes/index.js to the router created in
step 1.
Solution
Copy
// backend/user/index.js
const express = require('express');
const userRouter = require("./user");
const accountRouter = require("./account");
router.use("/user", userRouter);
router.use("/account", accountRouter);
module.exports = router;
Copy
{
balance: 100
}
Solution
Copy
router.get("/balance", authMiddleware, async (req, res) => {
const account = await Account.findOne({
userId: req.userId
});
res.json({
balance: account.balance
})
});
Copy
{
to: string,
amount: number
}
Response:
Status code - 200
Copy
{
message: "Transfer successful"
}
Copy
{
message: "Insufficient balance"
}
Copy
{
message: "Invalid account"
}
Copy
router.post("/transfer", authMiddleware, async (req, res) => {
const { amount, to } = req.body;
if (!toAccount) {
return res.status(400).json({
message: "Invalid account"
})
}
await Account.updateOne({
userId: req.userId
}, {
$inc: {
balance: -amount
}
})
await Account.updateOne({
userId: to
}, {
$inc: {
balance: amount
}
})
res.json({
message: "Transfer successful"
})
});
Copy
router.post("/transfer", authMiddleware, async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
const { amount, to } = req.body;
if (!toAccount) {
await session.abortTransaction();
return res.status(400).json({
message: "Invalid account"
});
}
// Perform the transfer
await Account.updateOne({ userId: req.userId }, { $inc: { balance: -
await Account.updateOne({ userId: to }, { $inc: { balance: amount }
Problems you might run into If you run into the problem mentioned above, feel free to
proceed with the bad solution
https://stackoverflow.com/questions/51461952/mongodb-v4-0-transaction-
mongoerror-transaction-numbers-are-only-allowed-on-a
Final Solution
Finally, the account.js file should look like this
Copy
// backend/routes/account.js
const express = require('express');
const { authMiddleware } = require('../middleware');
const { Account } = require('../db');
res.json({
balance: account.balance
})
});
session.startTransaction();
const { amount, to } = req.body;
if (!toAccount) {
await session.abortTransaction();
return res.status(400).json({
message: "Invalid account"
});
}
res.json({
message: "Transfer successful"
});
});
module.exports = router;
Copy
// backend/routes/account.js
const express = require('express');
const { authMiddleware } = require('../middleware');
const { Account } = require('../db');
const { default: mongoose } = require('mongoose');
res.json({
balance: account.balance
})
});
session.startTransaction();
const { amount, to } = req.body;
if (!toAccount) {
await session.abortTransaction();
console.log("Invalid account")
return;
}
transfer({
userId: "65ac44e10ab2ec750ca666a5",
body: {
to: "65ac44e40ab2ec750ca666aa",
amount: 100
}
})
transfer({
userId: "65ac44e10ab2ec750ca666a5",
body: {
to: "65ac44e40ab2ec750ca666aa",
amount: 100
}
})
module.exports = router;
Error
Get balance
Make transfer
Import react-router-dom into your project and add the following routes -
Solution
Copy
function App() {
return (
<>
<BrowserRouter>
<Routes>
<Route path="/signup" element={<Signup />} />
<Route path="/signin" element={<Signin />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/send" element={<SendMoney />} />
</Routes>
</BrowserRouter>
</>
)
}
Step 2 - Create and hook up Signup
page
Show the user their balance, and a list of users that exist in the database
Clicking on Send money should open a modal that lets the user send money
Step 5 - Auth Components
Full Signup component
You can break down the app into a bunch of components. The code only contains the styles
of the component, not any onclick functionality.
1. Heading component
Code
Copy
export function Heading({label}) {
return <div className="font-bold text-4xl pt-6">
{label}
</div>
}
Code
Copy
export function SubHeading({label}) {
return <div className="text-slate-500 text-md pt-1 px-4 pb-4">
{label}
</div>
}
3. InputBox component
Code
Copy
export function InputBox({label, placeholder}) {
return <div>
<div className="text-sm font-medium text-left py-2">
{label}
</div>
<input placeholder={placeholder} className="w-full px-2 py-1 borde
</div>
}
4. Button Component
Code
Copy
export function Button({label, onClick}) {
return <button onClick={onClick} type="button" class=" w-full text-w
}
5. BottomWarning
Code
Copy
import { Link } from "react-router-dom"
Code
Copy
import { BottomWarning } from "../components/BottomWarning"
import { Button } from "../components/Button"
import { Heading } from "../components/Heading"
import { InputBox } from "../components/InputBox"
import { SubHeading } from "../components/SubHeading"
Code
1. Appbar
Copy
import { BottomWarning } from "../components/BottomWarning"
import { Button } from "../components/Button"
import { Heading } from "../components/Heading"
import { InputBox } from "../components/InputBox"
import { SubHeading } from "../components/SubHeading"
2. Balance
Code
Copy
export const Balance = ({ value }) => {
return <div className="flex">
<div className="font-bold text-lg">
Your balance
</div>
<div className="font-semibold ml-4 text-lg">
Rs {value}
</div>
</div>
}
3. Users component
Code
Copy
import { useState } from "react"
import { Button } from "./Button"
return <>
<div className="font-bold mt-6 text-lg">
Users
</div>
<div className="my-2">
<input type="text" placeholder="Search users..." className="
</div>
<div>
{users.map(user => <User user={user} />)}
</div>
</>
}
function User({user}) {
return <div className="flex justify-between">
<div className="flex">
<div className="rounded-full h-12 w-12 bg-slate-200 flex jus
<div className="flex flex-col justify-center h-full text
{user.firstName[0]}
</div>
</div>
<div className="flex flex-col justify-center h-ful">
<div>
{user.firstName} {user.lastName}
</div>
</div>
</div>
4. SendMoney Component
Code
Copy
export const SendMoney = () => {
return <div class="flex justify-center h-screen bg-gray-100">
<div className="h-full flex flex-col justify-center">
<div
class="border h-min text-card-foreground max-w-md p-4 sp
>
<div class="flex flex-col space-y-1.5 p-6">
<h2 class="text-3xl font-bold text-center">Send Money</h
</div>
<div class="p-6">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 rounded-full bg-green-500 flex
<span class="text-2xl text-white">A</span>
</div>
<h3 class="text-2xl font-semibold">Friend's Name</h3
</div>
<div class="space-y-4">
<div class="space-y-2">
<label
class="text-sm font-medium leading-none peer-dis
for="amount"
>
Amount (in Rs)
</label>
<input
type="number"
class="flex h-10 w-full rounded-md border border
id="amount"
Step 7 - Wiring up the backend calls
placeholder="Enter amount"
/>
</div>
You can use <button class="justify-center rounded-md text-sm fon
Initiate Transfer
1. fetch or </button>
</div>
2. axios
</div>
to wire up calls to the backend server.
</div>
</div>
The final code looks something like this -
</div>
https://github.com/100xdevs-cohort-2/paytm/tree/complete-solution
} (complete-solution branch
on the repo)