Welcome to CryptoTalk.
This full stack web application was built using the PERN Stack.
Technologies: PostgreSQL, Express.js, React.js, Node.js, JavaScript, HTML, CSS, SCSS
The backend has been moved from Heroku to Adaptable.io
- Frontend: https://cryptotalk-cl.netlify.app/
- Backend: https://cryptotalk-cl.adaptable.app/
This is a personal project that I have been continuously working on since June 2022.
Reflecting on my journey, I'm thrilled to share the evolution of CryptoTalk. This used to be a very different app. Following demo day, I made a bold decision to reset and reimagine my group's initial project idea. I made a detatched fork of the original capstone, and contemplated on the future of this app. I decided to remove nearly the entire code base, and started over. I wanted to make sure the project was entirely my own, and that I had a thorough understanding of each line of code.
Thus, a new CryptoTalk was born—a dynamic, educational app designed to enlighten users about cryptocurrencies, help them discover their preferred coins, and enjoy crypto-inspired games.
From the start, I've been on a relentless path of improvement, continuously enhancing features and adding new dimensions to the app. At the upcoming project showcase, I'll be diving into the core features of CryptoTalk through an engaging user story, offering a glimpse into its transformative power.
- Welcome
- About CryptoTalk
- Table of Contents
- Overview
- Getting Started
- Frontend
- Backend
- One-to-many relationship (backend)
- Backend Routes
- CoinMarketCap API (where I got my data from)
- Data used in this app
- newsletter signup
- Protected Routes
- Miscellaneous Resources
- Notes
After forking and cloning this project, run these commands on the backend:
npm i
You will need a .env
file for the backend (with these fields completed)
PORT=
PG_HOST=
PG_PORT=
PG_DATABASE=
PG_USER=
PG_PASSWORD=
First, cd
into the backend directory using the terminal.
To run the backend, you'll find these scripts in the package.json
file.
Run them in this order to initialize the database, seed the database, and then start the backend.
npm run db:init
npm run db:seed
npm start
Go to your browser, and go to
http://localhost:[name of backend port]
example: http://localhost:3333/
After forking and cloning this project, run these commands on the frontend:
npm i
In a previous version of this app, Material UI was used. It has been removed.
In case there are issues with Material UI, run this command:
npm install @mui/material @emotion/react @emotion/styled
This app will now be using CSS and SASS. Node-Sass:
npm install node-sass
npm install react-icons --save
Example: Mobile layout - collapsible menu icon
https://www.chartjs.org/docs/latest/charts/line.html https://www.npmjs.com/package/react-chartjs-2 https://upmostly.com/tutorials/how-to-use-chart-js-with-react
npm install chart.js
npm i react-chartjs-2
npm install axios
First, cd
into the frontend directory using the terminal.
To run the frontend, you'll find this script in the package.json
file.
npm start
This should open up automatically in the browser, but if it doesn't, go to
http://localhost:[name of frontend port]
example: http://localhost:3000/
I removed all Material UI from the first version of this app (with my capstone group), after I forked the project to make it my own. I am adding it back in thoughtfully, to help develop my understanding of what is necessary for each component and factoring in how much time and effort it would take to create these components from scratch to achieve similar results.
Material UI is an open-source React component library that implements Google's Material Design. It includes a comprehensive collection of prebuilt components that are ready for use in production right out of the box.
I am using Material UI for these components:
- Login Modal, for modal
- UserNewForm, for username availability alerts
npm install @mui/material @emotion/react @emotion/styled
for Loop icon (LoginButton
component):
npm i @mui/icons-material
- Create instance with elephantSQL.
- Add tables with seed data in "browser" tab on left.
- SQL Query - copy & paste the tables from schema
- SQL Query - copy & paste the tables from seed
- open in Postico to view tables
- Adaptable.io
- add GitHub repository with node/postgres
- go to settings in adaptable, edit "Runtime Environment"
DATABASE_URL
value: add the elephantSQL URLpostgres://......
PG_HOST
value: elephantSQL server (without the parentheses)[name].db.elephantsql.com
PG_PORT
value:5432
PG_DATABASE
value: elephantSQL User & Default databasePG_USER
value: elephantSQL User & Default databasePG_PASSWORD
value: elephantSQL password
- add environment varialbe
REACT_APP_API_URL=
deployed adaptable link (without backslash at the end) - add environment variable
CI
with value offalse
(lowercase) - Clear cache & deploy site -> deployment is DONE, check deployed Netlify link that it works!
Models
Each model is represented by a table in the database.
- Users
- Currencies
Relationships
The tables have a one-to-many relationship
- A user has many currencies
- Each currency has a user
The backend database using Postgres has three tables: users
, resources
, and favorites
. These are the routes for each table:
Resource | Method | Route | Description |
---|---|---|---|
users | GET | /users | Get all users . |
users | POST | /users | Create a users . |
users | GET | /users/{uid} | Return one users by their id of {uid}. |
users | PUT | /users/{uid} | Update one users by their id of {uid}. |
users | DELETE | /users/{uid} | Delete one users by their id of {uid}. |
Resource | Method | Route | Description |
---|---|---|---|
currencies | GET | /coins | Get all currencies that exist in the database. |
currencies | GET | /coins/{cid} | Get one currencies by id of {cid} for every coin that exists in the database. |
Resource | Method | Route | Description |
---|---|---|---|
currencies | GET | /currencies | Get all currencies by id of {userId} that belong to a user. |
currencies | GET | /currencies/{cid} | Get one currencies by id of {cid} of a specific user. |
currencies | POST | /currencies | Create a currencies |
currencies | PUT | /currencies/{cid} | Update one currencies by id of {cid} |
currencies | DELETE | /currencies/{cid} | Delete one currencies by id of {cid} |
Resource | Method | Route | Description |
---|---|---|---|
nfts | GET | /nfts | Get all nfts . |
nfts | GET | /nfts/{nid} | Return one nfts by their id of {nid}. |
I'm not using the API directly for this project, but instead using it as a starting point, so I can have some actual real-world data to work with.
CoinMarketCap API Documentation
testing with sandbox environment (has mock data only):
sandbox-api.coinmarketcap.com
To get real data, replace sandbox with:
https://pro-api.coinmarketcap.com
- Preferred method: Via a custom header named
X-CMC_PRO_API_KEY
- Convenience method: Via a query string parameter named:
CMC_PRO_API_KEY
test API Key:
b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
Example link to fetch the data:
https://sandbox-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
The data used in this app comes from the coinmarketcap API.
// 20220713205413 - July 13, 2022 8:54pm
Stored in backend file: cryptoListingsLatest.json
for now
https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=
(add API Key to the ending of this URL in browser)
for backend:
npm install request
npm install body-parser
- Reminder: Don't push the mailchimp API key to GitHub, or it will become inactive and will need to create a new one.
frontend:
- Make new component for signup that takes in firstname, lastname, email.
- New component has form, and hooks in React state for each input field
- axios post request to backend using the values from user input, which sends the user inputs to the backend that will post to mailchimp to be stored.
- Style the SCSS for the functional Signup component
2-26-23
adding cookie-parser
on backend
npm install cookie-parser
Add to app.js:
var cookieParser = require('cookie-parser');
and
app.use(cookieParser());
Add Toasts to show user with Material UI
https://mui.com/material-ui/react-alert/#toast
App.js
:
Create toast with a useEffect() and give it a setTimeout() for 3 seconds
- Set message when users log in
LoginModal.js
pass in setLoginMessage
as a prop
- Then pass as prop into children/forms:
LoginAccountForm.js
andCreateAccountForm.js
Using bcrypt: A library to help you hash passwords.
npm install bcrypt
Go to users route, set variable
const bcrypt = require('bcrypt');
Go to POST route
An implementation of JSON Web Tokens.
This was developed against draft-ietf-oauth-json-web-token-08
. It makes use of node-jws
What is a JWT? https://supertokens.com/blog/what-is-jwt Three parts: Header, Payload, Signature
Install jsonwebtoken
npm install jsonwebtoken
make utils folder
file: jwt-helpers.js
create tokens, for example (not full code - see file):
const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, {expiresIn: '30d'});
const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, {expiresIn: '30d'});
add to .env
file - values can be anything
ACCESS_TOKEN_SECRET=randomTextHere
REFRESH_TOKEN_SECRET=somethingElseHere
(if not done already) install dotenv
npm install dotenv
set up tokens with jwt-helpers.js
file - helper function
and send data
Listening on port 3333
Error: data and salt arguments required
at Object.hash (/Users/...<path here>.../capstone-app/backend/node_modules/bcrypt/bcrypt.js:137:17)
at /Users/...<path here>.../capstone-app/backend/node_modules/bcrypt/promises.js:29:12
at new Promise (<anonymous>)
What is "salt" heynode.com Before hashing a password, I need to apply a salt. A salt is a random string that makes the hash unpredictable.
Bcrypt is a popular and trusted method for salt and hashing passwords. You have learned how to use bcrypt's NodeJS library to salt and hash a password before storing it in a database. You have also learned how to use the bcrypt compare function to compare a password to a hash, which is necessary for authentication.
^^ Christina says:
- Moved query into
usersController
and error is now gone? - still getting same error on frontend when making a POST request.
As of 1/28/23:
UserNewForm.js
creates a new user with hashed password, but is not connected to the LoginModal.js
in any way
CreateAccountForm.js
doesn't create any user at all for some reason
^^ Issue resolved. The query on the backend had an error. It needed the variables to use the exact spelling as in the schema to work properly since I needed user_password
instead of password
for example.
Frontend: Error handling for when creating a new user:
- username must be unique not null (unique on backend schema as well)
- email must be unique (unique on backend schema as well)
To Do: Protected routes, to give access to only the user that is logged in.
A user needs to be logged in to access a particular route Example: An app has two routes:
- /videos which is a page that lists videos
- /profile is a page that shows a user's profile when logged in Example: /videos is a "hidden" route to a page with links to videos When a user is logged in, show a link for /videos When a user is logged out, can't see the link to /videos Example: /profile is a protected route Can only get to /profile if the user is logged in
Questions to ask: What do you want to show someone when they're logged in? What do you not want people to access when they're not logged in? Example: /profile has user information about a user that you wouldn't want other users to see if they're not logged in
- Want to only fetch that user's information if the user is logged in
Protected Route:
http://localhost:3333/accounts
this is a route where a user needs to be logged in to access this route
Middleware - Authorization.js
is middleware that goes into the AccountsController.js route.
accounts.get('/', authenticateToken, (req, res) => {
...// code here
}
It verifies the token before letting the user proceed to having access to the route.
// _ You need to be logged in to acccess this route _
// Example: http://localhost:3333/accounts
// get all accounts of all users
// json object has an accountNumber
and accountBalance
for each user as an array
// this information is not in the database, want to make sure a user can only get it if they are logged in
// middleware - will be in the middle of the route and callback function, call it "authenticateToken"
// if the token is authenticated, continue with the route
Make sure they work properly when user is logged in/not logged in
- Updating both backend and frontend to create additional protected routes
- TODO: make page re-render after a user logs in
npm i use-local-storage
Local storage allows the theme to be stored, so a user can keep seeing the "light" or "dark" mode that was chosen, after page reload.
The recommended dark theme surface color by Material Design is: Dark Grey — #121212. Dark Mode UI Design: A Complete Guide
Tooltip text: HTML title Attribute
- Capstone project: May - June 2022
- Contributors: Myself, and 3 other capstone team members
- June 2022 - Present: I forked my own copy of this capstone project to be a personal project, where it is detatched from the original capstone group.
- All other contributors are detatched, and cannot collaborate on my forked copy of this project.
- Contributors: Myself ( IMPORTANT: This means only 1 person is working on this project, which is only me, the repository owner.)
- I removed all frontend components built by the capstone group
- I removed all backend tables and routes from the backend, aside from the main table
users
which I pair-programmed with, and although my GitHub account didn't "push" the code for this one table specifically, I would have coded it the same exact way. - Backend: Every table, query, and route on the backend is now my original code.
- Frontend: Every component on the frontend is now my original code.
I have been continuously adding to this project weekly, as it is a personal project that I am:
- making improvements
- adding new features
- improving those features
- fixing bugs
- creating bugs
- testing my code to make sure it works
- re-deploying both the backend and frontend. Please visit the deployed link.
accessing Jwt accessToken on frontend for newspaper.js
npm i js-cookie
This app has Jest testing.
Jest Supertest testing on backend:
app.test.js
- integration testing
(see below for regular endpoints without tags - will need to fix this in the README.md later to combine/organize tables)
Resource | Method | Route | Description |
---|---|---|---|
currencies | GET | /coins | all coins |
currencies | GET | /coins?include=tags | all coins with tags |
currencies | GET | /coins/{cid} | coin by coin id |
currencies | GET | /coins/{cid}?include=tags | coin by coin id with tags |
currencies | GET | /users/1/currencies | users with currencies |
currencies | GET | /users/1/currencies?include=tags | users with currencies and tags |
Because Postgres is a relational database, the database cannot use an ARRAY[] data type in PostgreSQL.
The currencies
table has this:
tags TEXT []
which is wrong. You should not do this in a relational database such as PostgreSQL.
Instead, do this: Eliminate the array data type in the schema, and update everything.
- Create New table:
tags
- Update
schema.sql
- Update currencies table seed data, add tags table seed data in
seed.sql
- Create/Update queries
- Create/Update controllers
- Test that it works, test all endpoints for getting correct results.
- Test all queries, that are using
?include=tags
To Do: This breaks anywhere "tags" are used on the frontend. Will need to re-connect anywhere "tags" are used on the frontend.
- What is the
/authenticate
route doing in theusersController.js
file? - Does it do anything?
- When testing it on backend only, returns error.
- When testing it with frontend, still an error. Trying to show me a user named undefined that doesn't exist
users.get('/authenticate', async (req, res) => {
On the backend it gives me this:
// http://localhost:3333/users/authenticate
"Error: User ID not found"
On backend, getting error:
// http://localhost:3333/accounts
{
"error": "Null Token"
}
This is because the json web token doesn't get stored in local storage on the backend. The user is "logged in" but there's no way to tell, since the token isn't being saved on the backend once it's made, so it can't be checked for on the backend.
- Is there a way to save on the backend that the "token does exist"? To know whether or not a user has been logged in, on the backend only?
- Right now, the backend only sees that there is no token, so it responds with an error saying that the token is a "null token".
Password validation - must be 6 characters or more
- Make changes on both backend and frontend
- backend: check password length & set error message
- frontend: set error message for user to see
A cookie is better than using local storage.
Frontend: CreateAccountForm.js
Switching to using a cookie
instead of localStorage.setItem()
/*
- set the access token in localStorage to tell us the user is logged in
- localStorage.setItem("accessToken", data.accessToken);
- show that the user is logged in (specifcally the "log in"/"log out" button)
Replacing `localStorage.setItem("accessToken".....` with a cookie
- setting a cookie syntax:
*/
// save access token as a cookie
document.cookie = "accessToken=" + data.accessToken;
loginAccountForm.js
make changes with cookie
Replacing this:
localStorage.setItem('accessToken', data.accessToken);
...with a cookie:
// save access token as a cookie
document.cookie = "accessToken=" + data.accessToken;
Check the NavBar
App.js
- loggedIn/setLoggedIn
hook
App is still using localStorage, and I want to change it to a cookie.
Make a new file:
src > utils > cookieUtils.js
After changing the hook for loggedIn/setLoggedIn
, checking in browser:
- a user is logged in
- the
cookie
has replacedlocalStorage
- the
accessToken
is now incookies
, and is not inlocalStorage
anymore.
Todo: fix the logout functionality so that the accessToken
in cookies
can be removed.
Navbar.js
- in logOut, delete cookie accessToken