Jafar Shora
Jafar Shora
In this thesis, the theoretical background of the chosen technology stack was
provided, and the application specifications were identified. Furthermore, the
environment setup and the project implementation were documented, and the
resulting application was analysed. The thesis concluded with the writer’s evaluation
of the requirements fulfilment and gathered experience along the development
process.
List of Abbreviations
1 Introduction 1
2 Background 2
4 Project Implementation 13
5 Conclusion 33
References 34
Appendices
I/O: Input/output
1 Introduction
1) a frontend with login page and navigation bar to route between different
views.
2) a main view allowing the user to upload the products ranking JSON file,
filter the desired products, draw line charts from the filtered data, and
export the selected data on the charts as a .csv file.
2 Background
2.1.1 HTML
HTML is a markup language that is responsible for defining the structure of the
content of a web application. It encloses parts of the content in elements or tags
and dictates the way they appear or act. (MDN Web Docs, no date)
Throughout its history, HTML has been moving towards providing more
semantic markup, division of design and content, and accessibility as well as
the support for rich media experience. (HTML.COM, no date)
2.1.2 CSS
A typical HTML element and a CSS selector as its attribute is shown in figure 1.
2.1.3 JavaScript
When used in the browser, JavaScript uses a program called the engine that is
specific to each browser and can interpret JavaScript code. Through the
browser API, JavaScript can access and manipulate the Document Object
Model (DOM), which is a tree-structure hierarchy of HTML elements. (Sheikh,
2021)
4
Since its advent in 1995, when it was deployed to add interactivity to the
webpages, JavaScript has flourished into a highly functional language for
developing web, desktop, and mobile as well as building server-side
applications. Moreover, its open-source nature has allowed the emergence of
numerous frameworks including React, Angular, Vue, etc. for client-side, React
Native for Mobile, and namely Node.js for server-side development. (Michael,
2019)
An SPA loads a web document once and consequently updates just the DOM
elements through API calls when there are to be changes in the content. This
means that JavaScript changes parts of one HTML file dynamically. To navigate
between different pages, HTML History API, provides the navigation between
different views. (Johansson, 2020)
2.2.2 Node.js
construct that is inherent to JavaScript, Node waits for the response and
resumes just after receiving it. (nodejs, no date)
Node also comes with Node Package Manager (npm), which is a software
registry. npm is used to share third-party, open-source dependencies that are
basically packages of pre-written code. These packages provide access to
libraries that facilitate the application development. Most Web applications
contain a package.json file that hold the name of the dependencies every
project needs to run. As npm comes with a CLI tool, running npm install
command in its CLI, downloads and installs the specified dependencies in a
directory called node_modules. (Johansson, 2020)
2.3.1 React
React is a free and open-source frontend JavaScript library for building user
interfaces (React, no date). It can isolate different parts of a whole web
application into separate building blocks called components. React is mainly
focused on creating Views and does not favor any specific architectural pattern
when it comes to data management. (Johansson, 2020)
Added to React since the release of version 16.8., Hooks are functions that
enable functionalities, that had been only possible in class components, to be
7
2.3.2 Redux
Redux keeps the state data in its single store that serves as a “Single source of
truth” for the entire application. Views can be subscribed to this store and can
be changed according to state changes in the store. To decide over the
changes, Redux relies on pure functions, called Reducers, that evaluate
changes that are dispatched through Action objects. Every Action has a type
and may or may not have a payload. Upon receiving an Action, the reducer
changes the store according to the type of the Action. This consequently
changes the Views that are subscribed to the store. (Redux, no date)
2.3.3 Electron
With the influx of libraries and frameworks in the last decade, JavaScript usage
has drastically grown. This growth and the help from Node.js have pushed
JavaScript beyond the realm of the browser. Electron is another framework that,
since its launch in 2013, has become one of the largely used frameworks for
desktop-application development. Some of the well-known desktop applications
9
made by Electron are Visual Studio Code, Slack, Skype, Discord, etc. (Perkaz,
2021)
2.3.4 Material UI
2.3.5 Express
2.4 Database
2.4.1 MongoDB
2.4.2 Mongoose
Mongoose is an Object Data Model (ODM) library for modelling data for
MongoDB. As MongoDB is schema-less NoSQL database, it stores a JSON-like
format document, which brings about the advantage of speeding up the
12
This chapter provides an overview of the application that was developed as the
basis for this thesis.
• a frontend with a login page and a navigation bar to route between different
views.
• a view allowing the user to upload the products rankings JSON file, filter
the desired products, draw line charts from the filtered data, and exporting
the selected data on the charts as a .csv file.
13
The data for the charts was provided via a JSON file and an open-source
Electron-React boilerplate was suggested by the project manager.
As for version control systems, Git was the obvious choice, and two separate
repositories were instantiated on GitHub to host both the backend and the
frontend code. The frontend repository was populated by cloning the git repository
of the suggested boilerplate.
Considering the size and the scope of the project, GitHub Flow approach taken;
that is to have a lean, simple branching policy. A “develop” branch was made and
every feature branch was immediately merged into it.
In the end, Node.js, and alongside it npm, were installed globally. This allowed
the installation of dependencies on the Electron-React boilerplate through the
“npm install” command.
4 Project Implementation
The implementation process started with designing a backend with Nodejs and
MongoDB Atlas to handle the authentication for the application. The endpoints
on the server were tested using Postman application, that is used for API testing
when there is no frontend available yet.
When the server was developed and running, based on the boilerplate that was
suggested for the project, the development of Electron app integrated with the
React frontend started.
14
Nodejs, Express and MongoDB Atlas were used to implement the backend for
the application. As it was discussed in the previous chapter, Node comes with
npm package manager, and using "npm init -y" command initializes a Node
project with a package.json file. This is the file that includes the dependencies
that a project need. Moreover, new dependencies can be installed and the
metadata about them can be recorded in the same file. Figure 7 shows the inside
of the package.json file for the server side of the application, the installed
dependencies, and the scripts that can be used in npm CLI to run tasks related
to the application.
As it can be noted, running the dev script via “npm run dev” command starts the
server, and it utilizes nodemon package that automatically restarts the server
after each change in the directory. Also, the list of dependencies contains all the
dependencies or npm packages that were installed by running the command
“npm install <packageName>” at the start of the project.
15
The structure for the app was based on a common, straightforward approach
that divides the routes, models, and the controllers into different directories.
Figure 8 shows the structure of the server application.
16
try {
const existingUser = await User.findOne({ email });
if (!existingUser)
return res.status(404).json({ message: 'User does not exist' });
if (!isPasswordCorrect)
return res.status(400).json({ message: 'Invalid Credentials' });
Controller functions are imported into routes files to be used as call back
functions when the endpoints are requested from the frontend. Listing 2 shows
the routes that serve the end points for sign-in and sign-up requests.
usersRouter.post('/signin', signin);
usersRouter.post('/signup', signup);
module.exports = usersRouter;
Listing 2. User routers in /routes/user.js
18
Express Router module is used to make a new router object and subsequently
two routes are assigned to it for different routes in the application. Now these
routes can be used as middleware by the web server.
Here, model refers to a Mongoose model that is used to save the data about a
user as a MongoDB document in MongoDB Atlas cloud database. The
Mongoose model for this purpose contains a Mongoose Schema, called
“userSchema”, that maps to the users collection in the database and tells it in
what shape to save the data. In the case of this application, four pieces of data
are needed to be saved in the database. As it is depicted in Listing 3,
userSchema defines the shape of the data in MongoDB and Mongoose “set”
module takes care of adding the id assigned by MongoDB to and deleting the
security-related data from the response user object
userSchema.plugin(uniqueValidator);
userSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString();
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.passwordHash;
delete returnedObject.refreshtoken;
},
});
Listing 3. The code snippet of Mongoose model
module.exports = mongoose.model('User', userSchema);
The Express server resides in “index.js” and is assigned to the variable “app”. It
is responsible to connect Mongoose to the database and binds the application
route middleware to an instance of the server. This can be seen in Listing 4.
19
app.use('/api/users', usersRouter);
mongoose
.connect(MONGODB_URI, {})
.then(() =>
app.listen(config.PORT, () =>
logger.info(`Server Running on Port: http://localhost:${config.PORT}`),
),
)
.catch((error) => {
logger.error('error connecting to MongoDB:', error.message);
});
Alongside JWT, bcrypt package is used to hash the password when the user
signs up to the application and to compare the password entered by the user at
the time, they are signing in to verify the existence of the credentials in the
database. The signup code can be seen in Listing 5.
20
try {
const existingUser = await User.findOne({ email });
console.log(result);
const token = jwt.sign({ email: result.email, id: result._id }, 'test', {
expiresIn: '1h',
});
As the application was made on the boilerplate, the Electron was installed as
development dependency in the package.json with the following command:
21
By Importing the app module from electron, the event-driven nature of Electron
allows assigning event listeners to the app that runs desired call back function
upon the fulfilment of the event. A snippet of the “main.dev.js” can be seen in
22
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true,
},
});
mainWindow.loadURL(`file://${__dirname}/index.html`);
app
.whenReady()
.then(createWindow)
.catch(console.log);
app.on('activate', () => {
if (mainWindow === null) createWindow();
});
As it was mentioned earlier, one expected functionality of the application was the
possibility to upload a JSON file and exporting a CSV file after data manipulation.
Two custom functions that were included inside the main process can be seen in
Listing 7. As it is depicted in the listing, “dialog” module from Electron is used to
trigger a native window. “uploadFile” function is exported from the main process.
23
Electron “remote” function allows the rendered to have run “uploadFile” from the
frontend.
“uploadFile” uses the helper function “openFile” to read the filesystem with fs
module from Node and responds with the “webContents” EventEmmiter. In the
case of opening a file from the filesystem, it sends an arbitrary named event,
along with the result, back to the renderer or the frontend component. The
renderer uses Inter-process Communication (ipc) method to send messages to
the main process and listen to the messages that are sent through.
openFile(file);
};
console.log(file);
As it can be seen in Listing 8, the React component imports both “remote” and
“ipcRenderer” from Electron. It uses “remote” to gain access to the main
process function and uses “ipcRenderer” to listen for the event “file-opened”.
After the promise has been fulfilled the callback function stores the content of
the data in application state.
// Function Component
const HandleFiles = () => {
Exporting the file follows the same event-based system of Electron. In this case
data from the frontend is being sent back to the main process to be saved on
25
The last code snippet shows Electron’s ability in providing Node modules in the
client-side BrowserWindow. The aspect that is not available in web browsers.
As stated above, the mainWindow in the main process is an instance of the
BrowserWindow, which in the development environment opens up by running the
command “npm start” that is wired up in the boilerplate “package.json” file. The
React application loads up in the mainWindow.
The views that are included in the application can be divided as follows:
• Login page that also serves as the landing page of the application
• Dashboard page that will be shown after successful login and acts as the
homepage
Material UI
To have uniform design and save time, material UI was installed through the
following npm commands:
Login Page
Login page prompts the user to input their email and password. As it was
described in section 4.1.4, When the user presses the login button, and the
request goes to the backend, a JWT token will be generated and sent back to
26
This process can be seen in Listing 9. A promise based Axios call, named
“apiClient”, is made via the create method and it is intercepted to check if the
user is authenticated, in that case, the Authorization header of the
BrowserWindow will be populated by the token.
let BASE_URL;
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://localhost:3001/api';
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = '/api';
}
apiClient.interceptors.request.use((config) => {
const user = JSON.parse(localStorage.getItem('loggedUser'));
const token = user && user.token;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
}
Listing 9. Axios call to backend API with interceptors
The App component is where the routing between the different views takes
place. As it can be seen in Listing 10, components from “react-router-dom”
package have been imported and made the SPA able to switch between
different routes.
27
import './App.global.css';
import dgTheme from './configs/dgThemes';
import PrivateRoute from './components/PrivateRoute';
import { AuthRoutes, NonAuthRoutes } from './routes/paths';
import Auth from './layout/Auth';
For fulfilling the requirements for good practice, all the routes after authentication
are put in the “AppContent” component where they are mapped through in a
reusable fashion. Routes are stored in a “routes” file and imported to
“AppContent”. This simplifies the process when it comes to adding new routes to
the application. This can be seen in Listing 11.
28
Redux Integration
Redux was used to store the state of the app. Redux store keeps the data for
the authenticated user as well as RankCharts component. Two reducers were
used for authentication and rank data and as it is depicted in Listing 12, they
were combined in to make a root reducer.
Redux logger middleware was installed as a npm package to log the state of the
store on the console, as well as Redux Thunk to make Redux capable of returning
functions from action creators. In the end Redux store is exported to be used in
the “index.jsx”, where it wraps the App component with the help of Provider
component from “react-redux” package
29
RankCharts
For drawing the charts, React Recharts library was installed with the command
“npm install recharts” and deployed with React component. Listing 13 shows a
code snippet of “RanksLineChart”.
In development environment, running “npm start” in the root of the application will
launch the path “/” which is assigned to the sign in page. Figure 10 shows the
sign in page.
5 Conclusion
This thesis was based on the writer’s interest in web development technologies
and Darkglass’ need of an Electron application. The idea to employ the
knowledge of JavaScript and discover new and related topics was the main
source of motivation for the writer. The requirements of the proposed
application by the company were fulfilled to a reasonable extent. The user can
import JSON files of Rank data, filter the data to see a depiction of the products
ranks over time as line charts and export them as .csv file to be used in other
applications. Moreover, the working on new technologies and discussing issues
with the team provided a learning environment, that can be beneficial in further
developments.
References
HTML.COM (no date) HTML5 Basics for Everyone Tired Of Reading About
Deprecated Code. Available at: https://html.com/html5/#What_is_HTML5
(Accessed 10 September 2021)
npm trends (no date) angular vs jquery vs react vs vue. Available at:
https://www.npmtrends.com/angular-vs-jquery-vs-react-vs-vue (Accessed 13
September 2021)
Redux (no date) Redux Essentials, Part 1: Redux Overview and Concepts.
Available at: https://redux.js.org/tutorials/essentials/part-1-overview-concepts
(Accessed 20 September 2021)
dispatch(
selectChartData(
data.products.data.filter((p) => productNames.includes(p.name))
)
);
setProducts(productNames);
};
dispatch(deleteChip(innerText));
setProducts(products.filter((name) => name !== innerText));
};
Appendix 1
2 (2)
dispatch(
selectChartData(
data.products.data.filter((p) => productNames.includes(p.name))
)
);
setProducts(productNames);
};
dispatch(deleteChip(innerText));
setProducts(products.filter((name) => name !== innerText));
};