This website helps me to keep an overview of git repositories on different platforms, dev-, stage- and production-deployments and their particular hosting.
The data is stored in a MongoDB
and is accessed with a REST API
. The frontend is based on Vue.js
.
Personal mentoring project with Maria Husmann
Hochschule Luzern, Digital Ideation, HS2020
The sense behind this project is to try out and learn new technologies, concepts and libraries I never used before or don't know yet very well.
These are the key technologies I wanted to learn:
Based on these technologies, the architecture of the application can be split up in 3 parts.
npm run setup
Compiles and hosts server for development
npm run frontend
or
cd frontend
npm run serve
npm run frontend-build
or
cd frontend
npm run build
Connects to MongoDB and starts API-Server
npm run backend
or
cd backend
npm run dev
The backend server uses multiple enviroment variables, which are loaded by the library dotenv. Therefore create a .env
file in the backend/
folder.
PORT=4433 # API Port
MONGO_USER=<>
MONGO_PASSWORD=<>
MONGO_URL=h<>
MONGO_DB=<>
JWT_PRIVATEKEY=<>
Public Website: code.lergier.ch
API HTTP: api.code.lergier.ch
API HTTPS: hslu-code-project-collection.herokuapp.com
Code to deploy to Heroku:
git subtree push --prefix backend heroku master
The main view of the website is the project overview. Projects are grouped in categories and ordered by the date and their name.
If the user isn't logged in, the page shows less information.
If the user is logged in, the application shows more information.
If a user has an account he can login using his email and password.
Logged in users can create new projects.
View 3 was only a few weeks old when I started with this project.
I didn't experience big differences or improvements. For me the biggest change was using TypeScript
instead of normal JavaScript
.
To setup the project I used the newest version of Vue CLI with the following options:
Helpful Links:
In all my previous projects with a database I used a relational SQL-DB. So this is the very first time for me to try out a no SQL, file based database. Simply explained, in a noSQL Database the data isnβt stored in columns and rows but in files.
A lot of Tutorials use the Mongoose library to interact with the Database. After looking at the Node.js documentation from MongoDB I decided to use the native driver instead of this external library.
Documents are organized in Collections. In a SQL-DB, a collection would be a table and a row a document. Data is organized in field-value pairs, like key & value in JSON or a JS object. β
A document is stored in BSON
format, which means Binary JSON. BSON for example allows additional data types like Integer
, Long
, Float
, Date
.
Clusters: Group of servers that store your data.
Replica Set: A few connected machines that store the same data to ensure that if something happens to one of the machines the data will remain intact.
Every document in MongoDB has a unique _id
field: "_id": "..."
Command | Description |
---|---|
show dbs |
Show list of databases in the cluster |
use [databaseName] |
Go to a database inside Cluster |
show collections |
Show all collections (tables) inside the selected database |
db.[collectionName].find( {"state":"NY", "city":"ALBANY"} ) |
Search content in a table, shows matching documents. The query are the same as when used on data explorer (If step 2 is done, db is an alias for the selected database) |
[find(...)].count() |
Number of elements |
[find(...)].pretty() |
Prettify text to be easy readable |
db.[collectionName].findOne() |
Show random document |
db.[collectionName].insert({...}) db.[collectionName].insert([{ ... }, { ... }]) db.[collectionName].insert([{ ... }, { ... }], { "ordered": false }) |
Insert new document Without "ordered": false inserting will stop if there is an error |
db.[collectionName].updateMany({ "city": "HUDSON" }, { "$inc": { "pop": 10 } }) db.grades.updateOne({ "student_id": 250, "class_id": 339 } { "$push": { "scores": { "type": "extra credit", "score": 100 } } }) |
$inc: increment $set: new value $push: add new object to array |
db.[collectionName].deleteMany({ "test": 1 }) db.[collectionName].deleteOne({ "_id": 3 }) |
Delete document |
db.[collectionName].drop() |
Delete collection When all collections are dropped from a database, the database no longer appears in the list of databases when you run show dbs. |
To get startet with MongoDB I used MongoDB University. I made parts of the following tutorials:
- What is MongoDB (Nice basic examples)
- Structure Data for MongoDB
- MongoDB Node Driver
- Node QuickStart
- Node CRUD Operations (CRUD = Create, Read, Update, Delete)
- Node Driver Documentation
- Node Driver API
- MongoDB Data Modeling
- Schema Validation
- 1:n Relationships
For the Vue 3 frontend of my application I used Typescript.
I'm very used to write JS and it was somehow strange to change the way you write your normal code.
Because I already wrote code in typed languages like Java or Swift it wasn't really hard to understand how it works.
These are the most important dataypes in Typescript.
// Basic
let boolean: boolean = false;
let number: number = 8;
let string: string = "blue";
let notSure: unknown = 4;
noReturn = (): void => {}
// Array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
// Tuple
let x: [string, number] = ["hello", 10];
More Links:
To access and manage the database I wanted to write an API. This brings the benefits of an independent backend, so you can access the data from every client.
Code | Status |
---|---|
200 | OK |
404 | Not found |
500 | Internal Server Error |
201 | Created |
204 | No Content |
304 | Modified |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
409 | Conflict |
501 | Not Implemented |
π: Valid authentication header required
π§Ή: Parameters are sanitized by the server
βοΈ: Returns more information if authenticated
Returns all projects stored in the database. If valid authentication header is sent, the response contains more information.
Response 200: OK
:
{
"success": true,
"message": "projects",
"projects": [
{
"_id": "MongoDB ID",
"title": "string",
"category": "string",
"year": "number",
"private": "boolean",
"publicLink": {
"url": "string"
},
"repository": {
"platform": "string",
"name": "string",
"url": "string"
}
}
]
}
Returns the details of the project where the the provided :url
is equal to publicLink.url
. If valid authentication header is sent, the response contains more information.
Response 200: OK
:
{
"success": true,
"message": "project",
"projects": {
"_id": "MongoDB ID",
"title": "string",
"category": "string",
"year": "number",
"private": "boolean",
"publicLink": {
"url": "string"
},
"repository": {
"platform": "string",
"name": "string",
"url": "string"
}
}
}
Endpoint to create a new project. The request body must contain at least the values title
, category
, year
and private
.
Request:
{
"title": "string",
"category": "string",
"year": "number",
"private": "boolean",
"publicLink": { "url": "string", "tags": [ "string" ] },
"devLink": { "url": "string", "tags": [ "string" ] },
"repository": { "platform": "string", "url": "string", "tags": [ "string" ] },
"database": { "title": "string", "url": "string", "tags": [ "string" ] }
}
Response: 201: Created
{
"success": true,
"message": "project added successfully",
"project": {
"title": "string",
"category": "string",
"year": "number",
"private": "boolean",
"_id": "MongoDB Id"
},
"projectId": "MongoDB Id"
}
Endpoint to update a project. In the current version not implemented yet.
Endpoint to delete a project. The provided :id
needs to be the MongoDB ID from the specific project.
Response 200: OK
:
{
"success": true,
"message": "1 project successfully deleted"
}
Returns current user based on provided token.
Response 200: OK
:
{
"success": true,
"message": "User",
"token": "JWT",
"user": {
"_id": "MongoDB ID",
"name": {
"firstname": "string",
"familyname": "string"
},
"email": "string",
"authorizedByAdmin": "boolean"
}
}
Returns current user information based on :email
parameter. It's only possible to get the information of the logged in user.
Response 200: OK
:
{
"success": true,
"message": "user",
"user": {
"_id": "MongoDB ID",
"name": {
"firstname": "string",
"familyname": "string"
},
"email": "string",
"authorizedByAdmin": "boolean"
}
}
Endpoint to create a new user. If valid authentication header is provided, the option authorizedByAdmin
is set to true
, otherwise it's false
.
Endpoint to perform the login.
Request:
{
"email": "string",
"password": "string"
}
Response 200: OK
:
{
"success": true,
"message": "Login successfully",
"token": "JWT",
"user": {
"_id": "MongoDB ID",
"name": {
"firstname": "string",
"familyname": "string"
},
"email": "string",
"authorizedByAdmin": "boolean"
}
}
Endpoint to delete the user where the the provided :id
is equal to it's MongoDB Id
.
Response 200: OK
:
{
"success": true,
"message": "1 user successfully deleted"
}
For the Login authorization I use the great technology of JSON Web Tokens.
A JSON Web Token consists of 3 parts sperated by dots: Header
, Payload
and Signature
: xxxxx.yyyyy.zzzzz
Each part then is Base64Url
encoded.
When a users logs in successfully the API Server responds a JWT.
HTTP Request:
{
"email": "admin@lergier.ch",
"password": "myPassword"
}
Server Response:
{
"success": true,
"message": "Login successfully",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZmY3MmY4Y2MwYzg3ZTQ4MTA2NzAyYzYiLCJlbWFpbCI6ImFkbWluQGxlcmdpZXIuY2giLCJpYXQiOjE2MTAwNDk0MjcsImV4cCI6MTYxMDQ4MTQyN30.kwAqGeT9IFvX40QoST0T1r7JyPJ4XPKv_p_5nsfZXF4",
...
}
Analyzing the token using the debugger, it becomes clear what's stored inside it.
The important payload data in this response is the user information.
{
"userId": "5ff72f8cc0c87e48106702c6",
"email": "admin@lergier.ch",
"iat": 1610049427,
"exp": 1610481427
}
Using the right private key ``lets you verify the signature. This ensures that the data in the payload section wasn't changed.
To hash the password before saving it to the database I use bcrypt
. This makes the handling with passwords easy and safe. bcrypt
then offers nice functinos to compare the password of the user with the hashed one in the database.
To sanitize the user requests I use express-validator
as a middleware on the server.
If using Typescript:
Simple Approaches:
- https://medium.com/@onejohi/building-a-simple-rest-api-with-nodejs-and-express-da6273ed7ca9
- https://medium.com/weekly-webtips/how-to-create-a-rest-api-with-express-js-and-node-js-3de5c5f9691c
Tutorial Series from Robin Wieruch:
- The minimal Node.js with Babel Setup
- How to setup Express.js in Node.js
- How to create a REST API with Express.js in Node.js
- Setup MongoDB with Mongoose in Express
- Creating a REST API with Express.js and MongoDB
- How to handle errors in Express
For concept and design purposes I created a simple prototype using Figma.
Launch the prototype with this link: https://www.figma.com/file/5cJqJfXD5euCykWQKnMR2E/Home?node-id=0%3A1