Create a Full Stack Banking Application using React
Part 2: Create an application using PostgreSQL, Express, React and Node.js stack
This is part two of the series of building Full Stack banking application.
If you have missed part one, check that out HERE
In this article, we will continue to build the banking application by adding options to create a new bank account, allow users to deposit and withdraw an amount from the account and allow users to generate and download transaction reports in pdf format.
Let’s get started
Open server/scripts.sql
and add the account
and transactions
table scripts:
Create a new file Summary.js
inside components
folder and add the following content:
Create a new file AccountForm.js
inside components
folder and add the following content:
Create a new file Account.js
inside components
folder and add the following content:
Here, we are displaying the buttons for the deposit
, withdraw
, and summary
page.
Add a route for /account
inside the AppRouter.js
file:
<Route path="/account" component={Account} />
Now, inside Header.js
add link for Account
component:
Now, your account page will look like this:
Now, let’s add the forms to withdraw and deposit amount and to update the account details in AccountForm.js
file:
Now, we have forms to withdraw or deposit amount but we don’t have an option to add a bank account yet to withdraw or deposit amount from so let’s add a form to add a new bank account first.
Create a new file AddAccountForm.js
inside components
folder and add the following content:
Now, we will check if the account number exists and only then show the form to withdraw or deposit otherwise we will show the form to add an account.
Replace the contents of AccountForm.js
with the following content:
Create a new file account.js
inside src/actions
folder with the following content:
Create a new file mask.js
inside src/utils
folder with the following content:
This code will display only the last 4 digits of the account number and * will be displayed for other digits.
Create a new file account.js
inside server/routes
folder and add the following content:
Note, we are exporting here, Router
and getAccountByAccountId
function so we can use it in another route file without the need of re-writing the same code. This is because we need to call getAccountByAccountId
once while getting the transaction and another time while downloading the pdf report.
Add the account
route to server/index.js
file:
Create a new file account.js
inside src/reducers
folder with the following content:
Open store/store.js
and add account
reducer inside it:
Now, restart your application by running yarn start
and when you go to the account page and click on the withdraw or deposit button, you will be able to see add account form:
When the user clicks on Submit
button to add an account, then we need to call the initiateGetAccntDetails
function so that the user will be able to see the form to withdraw or deposit amount.
Therefore, we have added a return
statement inside try block of actions/account.js
file above for initiateAddAccntDetails
function:
return await post(`${BASE_API_URL}/account`, {
Because initiateAddAccntDetails
is declared as async
function, return
keyword will return a promise
from the function and in AccountForm.js
, inside handleAddAccount
function, we have added a .then
handler to call the initiateGetAccntDetails
function:
handleAddAccount = (account) => {
const { account_no, bank_name, ifsc } = account;
this.props
.dispatch(initiateAddAccntDetails(account_no, bank_name, ifsc))
.then(() => this.props.dispatch(initiateGetAccntDetails()));
};
Now, try adding a new account and verify that it’s working as expected.
You will also be able to update the account ifsc
code by clicking on “Edit Account Details”
link and entering new ifsc
code.
Now, create a new file transactions.js
inside server/routes
folder with the following content:
Here, whenever we are depositing
or withdrawing
any amount, we are adding the details in transactions
table and also updating the total_balance
column from account
table. So we are doing two things here.
If one of them fails then the other should not happen. If the transactions
table is updated but the total_balance
column value is not updated due to some error, then we need to revert the new data added in transactions
table and vice versa. So we are executing these two things as a transaction therefore instead of pool.query,
we are using client.query
here where the statements that need to be executed together will be added in between await client.query('begin')
and await client.query('commit')
. If any of the queries in between these calls fail, we call await client.query('rollback')
from inside catch block so the other transaction will be reverted means either both will succeed or none of them.
This will ensure that data will remain consistent in the table.
Open server/db/connect.js
and add getClient
function inside it and export it from the file
Add transactions
route to server/index.js
file:
Now, create a new file transactions.js
inside src/actions
folder with the following content:
Create a new file transactions.js
inside src/reducers
folder with the following content
Inside utils/constants.js
add following constants
export const SET_TRANSACTIONS = 'SET_TRANSACTIONS';
export const ADD_TRANSACTION = 'ADD_TRANSACTION';
export const SET_ACCOUNT = 'SET_ACCOUNT';
export const UPDATE_ACCOUNT = 'UPDATE_ACCOUNT';
Inside store/store.js
add transactions
reducer
Now, you will be able to withdraw
or deposit
amount to the bank account.
Awesome!
There is one issue though, When we login to the application using one user’s email address, the Redux store will contain that particular user information. Now when we do logout
and login
using another email address, we will see the same previous user account details displayed because we are not clearing the account information from the Redux store once the user logout from the application.
To understand it better, check out the below gif:
So to fix this, we need to clear the account information once the user logs out of the application.
To do that, open src/utils/constants.js
and add another constant:
export const RESET_ACCOUNT = 'RESET_ACCOUNT';
Inside actions/account.js
, add resetAccount
function:
export const resetAccount = () => ({
type: RESET_ACCOUNT
});
Inside the reducers/account.js
add another switch case
case RESET_ACCOUNT:
return {};
And now, open actions/auth.js
and inside initiateLogout
function, before calling return dispatch(signOut());
, dispatch resetAccount
action creator function.
export const initiateLogout = () => {
return async (dispatch) => {
try {
await post(`${BASE_API_URL}/logout`, true, true);
localStorage.removeItem('user_token');
dispatch(resetAccount());
return dispatch(signOut());
} catch (error) {
error.response && dispatch(getErrors(error.response.data));
}
};
};
Now, this will clear the Redux store data and we will get correct logged in user account details.
As you can see, the account information from Redux is cleared and as the second user does not have any account information, add account form is displayed
Now, we are all done with the withdraw and deposit account functionality, Let’s move on with the functionality to generate and download the transaction report.
Install the required dependencies from fullstack_banking_app
folder:
yarn add react-datepicker@3.0.0 react-bootstrap-table@4.3.1 downloadjs@1.4.7
Now, Install the required dependencies from server
folder:
yarn add ejs@3.1.3 puppeteer@3.3.0 moment@2.26.0
Open components/Summary.js
file and replace the contents with the following content:
Open src/index.js
and add react-bootstrap-table
and react-datepicker
CSS imports before main.scss
import statement:
import 'react-datepicker/dist/react-datepicker.css';
import 'react-bootstrap-table/dist/react-bootstrap-table-all.min.css';
Open actions/transactions.js
and add setTransactions
, initiateGetTransactions
and downloadReport
functions inside it:
Create a new file Report.js
inside components
folder with the following content:
Open server/routes/transactions.js
and add following routes:
Open server/utils/common.js
and add the following functions:
And export those functions from the file and import them in routes/transactions.js
file:
const { getTransactions, generatePDF } = require('../utils/common');
Create a new views
folder inside server
folder and add transactions.ejs
file inside it with the following content. (ejs
is templating engine we are using in Node.js to dynamically generate content)
To learn more about how to use and configure ejs
, check out my previous article HERE
Open server/index.js
and after app.use(express.json());
add the following line of code
app.set('view engine', 'ejs');
Now, start the server and React app by running yarn start
command and once you click on the summary page, you will see the following screen
You can select the date range for which you want the transaction report and click on Generate Report
button.
If there are no transactions during that period, you will see no transactions found message.
If there are transactions within those periods, then you will see the transactions in table format and will be able to download the report as pdf.
Let's understand the code now.
When we are clicking the “Generate Report”
button on the summary page, we are calling the /transactions/account_id
route by passing the selected start date
and end date
and get the list of transactions and add it to the redux store
and then we pass those transactions to Report.js
component and display it in table format.
Now, when we click on “Download Report”
button, we are calling the /download/account_id
route from routes/transactions.js
file by where we are taking the transactions list and passing the transactions list to transactions.ejs
file from server/views
folder and using ejs.compile
method to generate data with dynamic values from transactions array which is stored in the output
variable and then we are writing that output
html to the transaction.html
file and then we call the generatePDF
function to generate the pdf file using puppeteer
library by passing the generated transactions.html
file
The generated transactions.html
and transactions.pdf
will be stored in views
folder
If you want to learn details of how it’s generated using puppeteer
, check out my previous article HERE
Then once the pdf is generated, to download the pdf from the server, we are using the downloadjs
library by sending the content of pdf to download
function provided by downloadjs
library.
Here mentioning, { responseType: 'blob' }
for the axios
call is very important because this is a pdf file otherwise you will get a blank pdf file.
Note: The downloadjs
library is very powerful, we can download any type of file by just passing the content of the file as the first parameter to download
function, file to name as the second parameter, and type of file as the third parameter.
Now, we are done with the application. Let’s revisit the CORS issue.
If you remember, in the first part, we have added the following code in server/index.js
file
app.use(cors());
This allows us to make API calls to the server running on port 5000
from our React application running on port 3000
.
But adding CORS without any options will allow anyone to access your APIs, which is not good from the security point of view. So let’s see how can we remove the need of CORS at the server-side.
Open package.json
from fullstack_banking_app
folder and add proxy
property at the end of package.json
file
And remove the following line, as it’s not required now:
app.use(cors());
Now, open src/utils/constants.js
and change:
export const BASE_API_URL = 'http://localhost:5000';
To:
export const BASE_API_URL = '';
Now, restart the server and React app by running yarn start
command and If you go to http://localhost:3000/, you will see our React application and express APIs will also be available on port 3000
so there is no need of using CORS now. This is because adding proxy
allows create-react-app
to act as a proxy
and now all our server APIs will also run on port 3000
even though we are running the express app on port 5000
.
You can find Github source code until this point:
To secure this app from XSS
and CSRF
attacks check out the next part HERE.
That’s it for today. I hope you learned something new.
Don’t forget to subscribe to get my weekly newsletter with amazing tips, tricks, and articles directly in your inbox here.