A rewrite of my previous node project(povioNode) using Express.js instead of Sails.js with an increased focus of non-redundant code
The code will try to be as similar to the original as possible, which is why the documentation will be much more minimal.
Since express.js is used, there are less files and more hand-written code. Sequelize is responsible for /api/migrations, api/config/ and /api/seeders folders and files inside of that.
-
/index.js
- This file sets up the Express.js enviroment and prepares the server parameters
- We launch the server by using
node index.js
on this file
-
/routes/routes.js
- Our backbone file, this is the file where all the routes, and all the other middleware is defined.
- This file might get split into two files in the future
- The inner workings of this file will be explained further down
-
/models/user.js
- This is where the user model is defined. This file uses sequelize for all of its function
- Makes use of certain life cycle hooks as well. Namely beforeCreate and beforeUpdate, that takes care of hashing passwords in a slightly cleaner manner.
-
/models/like.js
- This is where the like model is defined. It is structured similarly to the user.js file.
- The life cycle hooks, namely afterDelete and afterCreate use manually written SQL sentences, because of "interesting" behaviour of incrementing in Sequelize.
- This is where sequelize.sync() is defined, which takes care of "creating" the database in case it doesn't already exist.
The major technologies/frameworks/tools used are:
- Express.js as the backend framework
- ElephantSQL(online FREE postgresql database hosting tool)
- Sequelize as the database communication framework
- JWT is used to set up authentication, currently has minimal security, only uses a secret.
- Bcrypt is used to hash+salt passwords and check logins
- json,body-parser
- Supertest for testing(tests.js)
Easiest and most fool proof way is the classic:
cd ~/source-folder/
npm install
node index.js
- Check terminal output for error messages, look for "Sync successful
- Test the endpoints out using a tool like Postman or cURL if you're feeling frisky Disclaimer: If you're testing my code from my database and you get weird outputs for certain usernames, passwords, because the database hasn't changed since the early days, before the program worked fine. Don't be scared of null outputs for certain usernames, or a unhashed password for those users.
This is amateurish code, but if you want to use it as a base for something better, the first step is customizing the /models/index.js.
What you want to change is :
sequelize = new Sequelize("postgres://popmmtnk:[email protected]:5432/popmmtnk",opts)
And set up your own database. I'll let you figure that out on your own using Sequelize's docs. The rest is up to reading the docs, reading comments and if things get really rough, opening an issue.
Both models are pretty self explanatory because they're not using anything but the basics. Hooks will be explained in the ##REST Endpoints part
Expected inputs in the API endpoints and their responses are described down below. Status 500 errors are not described. Generally, if you experience a status error with 500, you can't do much about it. The issue is with the server. Look for explanations in the response JSONs as you go.
Sign up to the system(username and password).
POST:
- username: username
- password: password
- Username and password must be longer than 4, shorter than 64 "
- response:
400
```json
{err:'Invalid input',status: 'Password or username is too short(must be longer than 4 characters, shorter than 32)'}
```
- Username must not already exist in the entry or general error with the database
- response:
400
```json
{err: 'Invalid input',status: 'Username already exists!'}
```
Invalid input errors take priority over errors with existing usernames, because the server internally checks the input before querying the database.
In case you have successfuly signed up the user, it generates the following response:
200
{status:'success',result:createdUser}
createdUser being the user object, which contains the user's id, username and likedByCount(should always be 0!).
Log in an existing user with a password
- username: username
- password: password
- User credentials must be correct
- 400
{err: "Authentication error",status: "Credentials don't exist!"'}
In case you have successfuly signed in the user, it generates the following response:
200
{status:'Success',token:jwt}
In case you don't add a token in the headers in x-www-form-urlencoded
token:YOURTOKENHERE
You received your token during login.
In case it's false:
403
{err: "Wrong token",
status: "Unverifiable token"}
In case you added no token:
403
{
err: "No token",
status: "Missing or unverifiable token!"
}
Get the currently logged in user information. This call needs to be authenticated.
- token : jwt
JWT: JSON Web Token you received when logging in. Token times out after 7 days
This mostly only fails due to lack of correct JWT.
Example of a succesful response
200
"status": "Success",
"id": 34,
"username": "david",
"likedByCount": 0
}
Change the logged in user's password.
- password : password
- Password is too short(under 4 or too long)
400
{
"err": "Invalid input",
"status": "Password or username is too short/long(must be longer than 4 characters, shorter than 64)"
}
Example of a successful call:
http://127.0.0.1:1337/api/me/update-password
Response:
200
{
"status": "Success",
"result": 1
}
:id could be a user's username or a users id.
Lists the usernames and number of likes of a user. This is not an authenticated call, which is why the body can be empty.
Example of a request:
or
http://127.0.0.1:1337/api/david14
Token rules apply.
- User doesn't exist:
400
{err: "User doesnt exist",
status: "The username or id you've requested does not exist"}
Example of a successful response
200
{
"status": "success",
"id": "4"
"username": "david14",
"likedByCount": 2
}
In which case the "likes" is the amount of people the user in question has liked.
"LikedByCount" is the amount of people that have been liked
Likes the user in question. This call needs to be authenticated
Example:
/user/davidv171/like
This means the user that's logged in likes the user davidv171.
Each user can like another user only once.
Example of a good call:
http://127.0.0.1:1337/api/user/1/like
or
http://127.0.0.1:1337/api/user/test2/like
GET Headers:
- token : jwt
- You can't like yourself:
400
{
err: "Cant like yourself",
status: "A user can only like other users, not himself"
}
- You can only like a existing user
400
{
"err": "User doesnt exist",
"status": "The username or id you've requested does not exist"
}
- You can't like a user you've already liked
400
{
"err": "Already liked",
"status": "You've already liked this user!"
}
http://127.0.0.1:1337/api/user/1/like would trigger a response:
200
{
"status": "Success",
"liked": "test"
}
"liked": always returns the id of the liked user.
Unlike a user that the logged in user has previously liked.
Example of a good call:
http://127.0.0.1:1337/api/user/1/unlike
or
http://127.0.0.1:1337/api/user/test2/unlike
GET Headers:
- token : jwt
- You can't unlike yourself:
400
{
err: "Cant like yourself",
status: "A user can only like other users, not himself"
}
- You can only unlike an existing user 400
{
"err": "User doesnt exist",
"status": "The username or id you've requested does not exist"
}
- You can't unlike a user you've already liked
400
{
"err": "Already unliked",
"status": "You already don't like this user"
}
200
{
"status": "Success",
"unliked": "test"
}
List users in a most to least liked manner.
Is not an authenticated call. This could always be tested first, because of this. If this call doesn't return a "200 OK" response, most likely nothing is going to work.
Example:
http://127.0.0.1:1337/api/most-liked
Is not an authenticated call. This could always be tested first, because of this. If this call doesn't return a "200 OK" response, most likely nothing is going to work.
```json
{
"status": "Success",
"leaderBoard": [
{
"id": 6,
"username": "express",
"likedByCount": 6
},
{
"id": 4,
"username": "neovim",
"likedByCount": 2
},
{
"id": 5,
"username": "test",
"likedByCount": 2
},
{
"id": 8,
"username": "express12",
"likedByCount": 1
},
{
"id": 1,
"username": "neovim12",
"likedByCount": 1
},
{
"id": 24,
"username": "aafy2t1",
"likedByCount": 0
},
{
"id": 25,
"username": "xddada",
"likedByCount": 0
},
{
"id": 26,
"username": "aliexpress",
"likedByCount": 0
},
{
"id": 3,
"username": "neovim121",
"likedByCount": 0
},
{
"id": 36,
"username": "david1",
"likedByCount": 0
},
{
"id": 37,
"username": "david13",
"likedByCount": 0
},
{
"id": 34,
"username": "david",
"likedByCount": 0
},
{
"id": 30,
"username": null,
"likedByCount": 0
},
{
"id": 7,
"username": "express1",
"likedByCount": 0
},
{
"id": 18,
"username": "xio51",
"likedByCount": 0
},
{
"id": 22,
"username": "test111",
"likedByCount": 0
},
{
"id": 23,
"username": "aafy2",
"likedByCount": 0
}
]
}
Postman takes care of 47 tests for us. They're not really automatic, they were used to test robustness more than to test the logic correctness.
I will try to implement supertest in the upcoming days.