This Node/Express RESTful API serves a frontend React App, getvocaltogov-frontend. The website acts as a vehicle lower barrier for citizens to petition their representatives and exchange ideas on current events. This readme provides information on the API portion of the service along with some context of the front-end app.
- This API:
- Provides full CRUD for 3 resources (User, Post, Template)
- Utilizes JSON Web Tokens to provide authorization
- It checks this authorization using middleware
- It provides Government Representatives based on User residential address
- Has robust test coverage using Jest Testing Library
- Utilizes Winston for event and error logging
- This was completed in approximately 60 hours as part of Springboard Software Engineering Program.
- Tech Stack
- Database Schema
- Deployment
- Developer
- Requests
- Running App Locally
- Testing
- Additional Steps
getvocaltogov-frontend React App Repo - React, Axios, React-Bootstrap, React Router, React Hook Form, Yup schema validation
Node.js, Express, Node-postgres, jsonwebtoken, jsonschema, bcrypt, winston, Axios, dotenv, colors, cors
Postgres
The Front-End React App is deployed on surge at https://getvocaltogov.surge.sh/
Please feel free to reach out!
- Email: [email protected]
Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/auth/register \
--header 'Content-Type: application/json' \
--data '{
"firstName": String,
"lastName": String,
"username": String,
"password": String,
"email": String,
"street": String,
"city": String,
"state": String,
"zip": String
}'
Response:
{ "token": String }
Sample Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/auth/register \
--header 'Content-Type: application/json' \
--data '{
"firstName": "testUser",
"lastName": "test",
"username": "user",
"password": "passGood",
"email": "[email protected]",
"street": "2210 oceanwalk dr w",
"city": "atlantic beach",
"state": "FL",
"zip": "32233"
}'
Sample Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJpc0FkbWluIjpmYWxzZSwiaWF0IjoxNjQyOTYzMzYwfQ.3dCWzXq60thKMTP3M_Pf37uR4GljqYOTjdRijNQEAII"
}
Retrieves a token for existing Users
Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/auth/token \
--header 'Content-Type: application/json' \
--data '{
"username": String,
"password": String
}'
Response:
{ "token": String }
Example Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/users/demoUser \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRlbW9Vc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MTkyOX0.mYkdokA8TbMD3qW9HbfnYR4wK9_aV6SYQ2yFUqnb8JQ'
Example Response:
{
"user": {
"username": "demoUser",
"password": "$2b$12$s3NJiNz1bbT2DZ56rWenredu60wGddT0qNR5WblylReVgoreFttdu",
"firstName": "Demo",
"lastName": "User",
"street": "2003 KLATTENHOFF DR",
"city": "AUSTIN",
"state": "TX",
"zip": "78728",
"email": "[email protected]",
"isAdmin": false,
"favorites": [
14,
11,
12
],
"bookmarks": [
4,
8,
5,
6
]
}
}
Sample Request:
curl --request PATCH \
--url https://getvocaltogov.herokuapp.com/users/testUser \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g' \
--data '{
"firstName": "changedname",
"lastName": "user",
"username": "testUser",
"password": "passGood",
"email": "[email protected]",
"street": "2210 oceanwalk dr w",
"city": "atlantic beach",
"state": "FL",
"zip": "32233"
}'
Sample Response:
{
"user": {
"username": "testUser",
"password": "$2b$12$A0CLfKS/wKKuu0TL5M9r5uWhb4EonWuQ9UydKhStPOl73K6lQzUuy",
"firstName": "changedname",
"lastName": "user",
"street": "2210 OCEANWALK DR W",
"city": "ATLANTIC BEACH",
"state": "FL",
"zip": "32233",
"email": "[email protected]",
"isAdmin": false,
"favorites": [],
"bookmarks": []
}
}
Sample Request:
curl --request DELETE \
--url https://getvocaltogov.herokuapp.com/users/testUser \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"deleted": "testUser"
}
Sample Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/users/testUser/templates/14 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
"favorited": 14
}
Sample Request:
curl --request DELETE \
--url https://getvocaltogov.herokuapp.com/users/testUser/templates/14 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"unfavorited": 14
}
Sample Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/users/testUser/posts/4 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"bookmarked": 4
}
Sample Request:
curl --request DELETE \
--url https://getvocaltogov.herokuapp.com/users/testUser/posts/4 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"unbookmarked": 4
}
A Post, in this context, is a User generated record containing information and commentary about a current event. Users can create then edit and/or delete Posts they own. Any User can read or bookmark/unbookmark a Post from the main Post feed. Posts are meant to create awareness of current events and to inspire Users to generate Templates to petition their Representatives. They consist of a title, body (to assert whatever the Post is about), link to article/reference (nullable), tag (to mark category), created_at (timestamp), and location (what state the post is referencing or if it is a federal issue, use District of Columbia).
Sample Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/posts \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g' \
--data '{
"title": "test title 2",
"link": "https://kdvr.com/news/coronavirus/omicron-variant-case-confirmed-in-boulder-county/",
"body": "we need to do q, r, s",
"tag": "Health Care",
"location": "FL",
"userId": "JDean1"
}'
Sample Response:
{
"post": {
"id": 9,
"title": "test title 2",
"link": "https://kdvr.com/news/coronavirus/omicron-variant-case-confirmed-in-boulder-county/",
"body": "we need to do q, r, s",
"userId": "testUser",
"tag": "Health Care",
"location": "FL",
"createdAt": "2022-01-23T19:10:50.989Z",
"templates": []
}
}
Sample Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/posts \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Response:
{"posts": [Object]}
Sample Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/posts/4 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"post": {
"id": 4,
"title": "Newsom backs away from single-payer health care pledge",
"link": "https://calmatters.org/commentary/2022/01/newsom-single-payer-health-care/",
"body": String,
"userId": "demoUser",
"tag": "Health Care",
"location": "CA",
"createdAt": "2022-01-18T19:40:36.143Z",
"templates": [
{
"id": 8,
"title": "Healthcare System needs change!",
"body": String,
"userId": "demoUser",
"createdAt": "2022-01-18T19:44:21.624Z",
"postId": 4
}
]
}
}
Sample Request:
curl --request PATCH \
--url https://getvocaltogov.herokuapp.com/posts/9 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g' \
--data '{
"title": "test title 2",
"link": "https://kdvr.com/news/coronavirus/omicron-variant-case-confirmed-in-boulder-county/",
"body": "edited body",
"tag": "Health Care",
"location": "FL",
"userId": "JDean1"
}'
Sample Response:
{
"post": {
"id": 9,
"title": "test title 2",
"link": "https://kdvr.com/news/coronavirus/omicron-variant-case-confirmed-in-boulder-county/",
"body": "edited body",
"userId": "testUser",
"tag": "Health Care",
"location": "FL",
"templates": []
}
}
Sample Request:
curl --request DELETE \
--url https://getvocaltogov.herokuapp.com/posts/9 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"deleted": 9
}
A Template, in this context, is a User generated title and body of an email one would send to their Representative. Here is a sample reference of how one could structure Template content. Users can create Templates in relation to that Post or independently unattached to a Post. They can then update and/or delete Templates they own. All Users can read and favorite/unfavorite Templates from the Template feed. They consist of a title, body (to assert whatever the Post is about), and created_at (timestamp).
Sample Request:
curl --request POST \
--url https://getvocaltogov.herokuapp.com/templates \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g' \
--data '{
"title": "test title 2",
"body": "we need to do q, r, s"
}'
Sample Response:
{
"template": {
"id": 18,
"title": "test title 2",
"body": "we need to do q, r, s",
"userId": "testUser",
"postId": null,
"createdAt": "2022-01-23T19:26:57.849Z"
}
}
Sample Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/templates \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{ "templates": [Object] }
Sample Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/templates/18 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"template": {
"id": 18,
"title": "test title 2",
"body": "we need to do q, r, s",
"userId": "testUser",
"postId": null,
"createdAt": "2022-01-23T19:26:57.849Z"
}
}
Sample Request:
curl --request PATCH \
--url https://getvocaltogov.herokuapp.com/templates/18 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g' \
--data '{
"title": "edited title",
"body": "we need to do q, r, s"
}'
Sample Response:
{
"template": {
"id": 18,
"title": "edited title",
"body": "we need to do q, r, s",
"userId": "testUser",
"postId": null,
"createdAt": "2022-01-23T19:26:57.849Z"
}
}
Sample Request:
curl --request DELETE \
--url https://getvocaltogov.herokuapp.com/templates/18 \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RVc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MzQ4Mn0.mkzJDI5dAVOS2Gpa2aPek6pXVhfzazKcAMUducIvx9g'
Sample Response:
{
"deleted": 18
}
When a User registers, they are required to enter their residential address. This address is verified through an external service before being stored in the database. The address is then used to retrieve the User's Government representatives, from the President of The United States to their local officials. The list of representatives along with their contact information can be found on the User's 'Profile' page under the 'Representatives' tab. There is variance in what contact information (address, phone #, email, web page) is available, but the maximum amount of contact information is displayed for each Representative.
Sample Request:
curl --request GET \
--url https://getvocaltogov.herokuapp.com/representatives/demoUser \
--header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRlbW9Vc2VyIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTY0Mjk2MTkyOX0.mYkdokA8TbMD3qW9HbfnYR4wK9_aV6SYQ2yFUqnb8JQ'
This route utilizes a wrapper that communicates to Google Civic Information API under the hood. See the documentation on retrieving representative information to understand more about what the 'Object' in the sample response below contains.
Sample Response:
{
"representatives": Object
}
Running Apps (getvocaltogov-frontend and backend) Locally
- Node.js, React
- PostgresSQL
- npm
Retrieve free API keys from:
-
Clone the repositories:
git clone https://github.com/bbeckenb/getvocaltogov-frontend.git
- this is the front-end server
git clone https://github.com/bbeckenb/GetVocalToGov.git
-this is the backend API
-
Open two separate terminal windows, navigate to the two projects individually:
cd getvocaltogov-frontend
cd GetVocalToGov
-
Install requirements in each project directory:
npm install
-
Set up local database:
createdb get_vocal_to_gov_db
-
Set up .env file in GetVocalToGov:
touch .env
-
Add the following fields and enter your information (Requires API key retrieval step) where it says YourInfo
GOOGLE_API_KEY=YourInfo EASY_POST_API_KEY=YourInfo EASY_POST_API_TEST_KEY=YourInfo SECRET_KEY=YourInfo
NOTE:
SECRET_KEY
can be whatever you want it to be, you can generate 16 random bytes of hex digits usinghexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/urandom
in the command line. -
Run Express API Application
- In terminal where you are in the 'GetVocalToGov' directory, type
npm start
- This will be on port 3001
- Run React front-end server
- In terminal where you are in the 'getvocaltogov-frontend' directory, type
npm start
- This will be on port 3000
- Testing for the Express API has pretty robust coverage, it uses Jest Testing Library.
- Inside of the GetVocalToGov directory, type
jest -i
to run through the test suites.
- Inside of the GetVocalToGov directory, type
- Testing for the front-end React App utilizes the React Testing Library and Jest Testing Library.
- Each component has its own test file to ensure it renders and performs the intended core functionality.
- To look at the test results of any individual component, inside of the getvocaltogov-frontend directory, type
npm test NameOfComponent.test.jsx
I built this application as part of SpringBoard's Software Engineering curriculum and put in ~60 hours. If I were to continue developing it, there would be several steps I would take.
I think it would be a much more well rounded app and more of a one-stop-shop if there was a news feed page that provided stories aligned with the categories you would create Posts for.
I added an is_admin boolean to the User model, but have not gotten around to adding an Admin portal on the web app itself. It would be much easier to manage data on the app, both test and real.