This project is a summary of this back-end trimester: authentication, NodeJS, MongoDB, Redis, pagination and background processing.
The objective is to build a simple platform to upload and view files:
- User authentication via a token
- List all files
- Upload a new file
- Change permission of a file
- View a file
- Generate thumbnails for images
You will be guided step by step for building it, but you have some freedoms of implementation, split in more files etc… (utils folder will be your friend)
Of course, this kind of service already exists in the real life - it’s a learning purpose to assemble each piece and build a full product.
Enjoy!
Read or watch:
- Node JS getting started
- Process API doc
- Express getting started
- Mocha documentation
- Nodemon documentation
- MongoDB
- Bull
- Image thumbnail
- Mime-Types
- Redis
At the end of this project, you are expected to be able to explain to anyone, without the help of Google:
- how to create an API with Express
- how to authenticate a user
- how to store data in MongoDB
- how to store temporary data in Redis
- how to setup and use a background worker
Click to show/hide file contents
Click to show/hide file contents
Click to show/hide file contents
Don’t forget to run $ npm install when you have the package.json
mandatory
Inside the folder utils, create a file redis.js that contains the class RedisClient.
RedisClient should have:
- the constructor that creates a client to Redis:
- any error of the redis client must be displayed in the console (you should use
on('error')of the redis client)
- any error of the redis client must be displayed in the console (you should use
- a function
isAlivethat returnstruewhen the connection to Redis is a success otherwise,false - an asynchronous function
getthat takes a string key as argument and returns the Redis value stored for this key - an asynchronous function
setthat takes a string key, a value and a duration in second as arguments to store it in Redis (with an expiration set by the duration argument) - an asynchronous function
delthat takes a string key as argument and remove the value in Redis for this key
After the class definition, create and export an instance of RedisClient called redisClient.
bob@dylan:~$ cat main.js
import redisClient from './utils/redis';
(async () => {
console.log(redisClient.isAlive());
console.log(await redisClient.get('myKey'));
await redisClient.set('myKey', 12, 5);
console.log(await redisClient.get('myKey'));
setTimeout(async () => {
console.log(await redisClient.get('myKey'));
}, 1000*10)
})();
bob@dylan:~$ npm run dev main.js
true
null
12
null
bob@dylan:~$
Inside the folder utils, create a file db.js that contains the class DBClient.
DBClient should have:
- the constructor that creates a client to MongoDB:
- host: from the environment variable
DB_HOSTor default:localhost - port: from the environment variable
DB_PORTor default:27017 - database: from the environment variable
DB_DATABASEor default:files_manager
- host: from the environment variable
- a function
isAlivethat returnstruewhen the connection to MongoDB is a success otherwise,false - an asynchronous function
nbUsersthat returns the number of documents in the collectionusers - an asynchronous function
nbFilesthat returns the number of documents in the collectionfiles
After the class definition, create and export an instance of DBClient called dbClient.
bob@dylan:~$ cat main.js
import dbClient from './utils/db';
const waitConnection = () => {
return new Promise((resolve, reject) => {
let i = 0;
const repeatFct = async () => {
await setTimeout(() => {
i += 1;
if (i >= 10) {
reject()
}
else if(!dbClient.isAlive()) {
repeatFct()
}
else {
resolve()
}
}, 1000);
};
repeatFct();
})
};
(async () => {
console.log(dbClient.isAlive());
await waitConnection();
console.log(dbClient.isAlive());
console.log(await dbClient.nbUsers());
console.log(await dbClient.nbFiles());
})();
bob@dylan:~$ npm run dev main.js
false
true
4
30
bob@dylan:~$
Inside server.js, create the Express server:
- it should listen on the port set by the environment variable
PORTor by default 5000 - it should load all routes from the file
routes/index.js
Inside the folder routes, create a file index.js that contains all endpoints of our API:
GET /status=>AppController.getStatusGET /stats=>AppController.getStats
Inside the folder controllers, create a file AppController.js that contains the definition of the 2 endpoints:
GET /statusshould return if Redis is alive and if the DB is alive too by using the 2 utils created previously:{ "redis": true, "db": true }with a status code 200GET /statsshould return the number of users and files in DB:{ "users": 12, "files": 1231 }with a status code 200userscollection must be used for counting all usersfilescollection must be used for counting all files
Terminal 1:
bob@dylan:~$ npm run start-server
Server running on port 5000
...
Terminal 2:
bob@dylan:~$ curl 0.0.0.0:5000/status ; echo ""
{"redis":true,"db":true}
bob@dylan:~$
bob@dylan:~$ curl 0.0.0.0:5000/stats ; echo ""
{"users":4,"files":30}
bob@dylan:~$
Now that we have a simple API, it’s time to add users to our database.
In the file routes/index.js, add a new endpoint:
POST /users=>UsersController.postNew
Inside controllers, add a file UsersController.js that contains the new endpoint:
POST /users should create a new user in DB:
- To create a user, you must specify an
emailand apassword - If the
emailis missing, return an errorMissing emailwith a status code 400 - If the
passwordis missing, return an errorMissing passwordwith a status code 400 - If the
emailalready exists in DB, return an errorAlready existwith a status code 400 - The
passwordmust be stored after being hashed inSHA1 - The endpoint is returning the new user with only the
emailand theid(auto generated by MongoDB) with a status code 201 - The new user must be saved in the collection
users:email: same as the value receivedpassword:SHA1value of the value received
bob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H "Content-Type: application/json" -d '{ "email": "[email protected]", "password": "toto1234!" }' ; echo ""
{"id":"5f1e7d35c7ba06511e683b21","email":"[email protected]"}
bob@dylan:~$
bob@dylan:~$ echo 'db.users.find()' | mongo files_manager
{ "_id" : ObjectId("5f1e7d35c7ba06511e683b21"), "email" : "[email protected]", "password" : "89cad29e3ebc1035b29b1478a8e70854f25fa2b2" }
bob@dylan:~$
bob@dylan:~$
bob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H "Content-Type: application/json" -d '{ "email": "[email protected]", "password": "toto1234!" }' ; echo ""
{"error":"Already exist"}
bob@dylan:~$
bob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H "Content-Type: application/json" -d '{ "email": "[email protected]" }' ; echo ""
{"error":"Missing password"}
bob@dylan:~$
In the file routes/index.js, add 3 new endpoints:
GET /connect=>AuthController.getConnectGET /disconnect=>AuthController.getDisconnectGET /users/me=>UserController.getMe
Inside controllers, add a file AuthController.js that contains new endpoints:
GET /connect should sign-in the user by generating a new authentication token:
- By using the header
Authorizationand the technique of the Basic auth (Base64 of the<email>:<password>), find the user associate to this email and with this password (reminder: we are storing the SHA1 of the password) - If no user has been found, return an error
Unauthorizedwith a status code 401 - Otherwise:
- Generate a random string (using
uuidv4) as token - Create a key:
auth_<token> - Use this key for storing in Redis (by using the
redisClientcreate previously) the user ID for 24 hours - Return this token:
{ "token": "155342df-2399-41da-9e8c-458b6ac52a0c" }with a status code 200
- Generate a random string (using
Now, we have a way to identify a user, create a token (= avoid to store the password on any front-end) and use this token for 24h to access to the API!
Every authenticated endpoints of our API will look at this token inside the header X-Token.
GET /disconnect should sign-out the user based on the token:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401 - Otherwise, delete the token in Redis and return nothing with a status code 204
- If not found, return an error
Inside the file controllers/UsersController.js add a new endpoint:
GET /users/me should retrieve the user base on the token used:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401 - Otherwise, return the user object (
emailandidonly)
- If not found, return an error
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"031bffac-3edc-4e51-aaae-1c121317da8a"}
bob@dylan:~$
bob@dylan:~$ curl 0.0.0.0:5000/users/me -H "X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a" ; echo ""
{"id":"5f1e7cda04a394508232559d","email":"[email protected]"}
bob@dylan:~$
bob@dylan:~$ curl 0.0.0.0:5000/disconnect -H "X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a" ; echo ""
bob@dylan:~$ curl 0.0.0.0:5000/users/me -H "X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a" ; echo ""
{"error":"Unauthorized"}
bob@dylan:~$
In the file routes/index.js, add a new endpoint:
POST /files=>FilesController.postUpload
Inside controllers, add a file FilesController.js that contains the new endpoint:
POST /files should create a new file in DB and in disk:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401
- If not found, return an error
- To create a file, you must specify:
name: as filenametype: eitherfolder,fileorimageparentId: (optional) as ID of the parent (default: 0 -> the root)isPublic: (optional) as boolean to define if the file is public or not (default: false)data: (only fortype=file|image) as Base64 of the file content
- If the
nameis missing, return an errorMissing namewith a status code 400 - If the
typeis missing or not part of the list of accepted type, return an errorMissing typewith a status code 400 - If the
datais missing andtype != folder, return an errorMissing datawith a status code 400 - If the
parentIdis set:- If no file is present in DB for this
parentId, return an errorParent not foundwith a status code 400 - If the file present in DB for this
parentIdis not of typefolder, return an errorParent is not a folderwith a status code 400
- If no file is present in DB for this
- The user ID should be added to the document saved in DB - as owner of a file
- If the type is
folder, add the new file document in the DB and return the new file with a status code 201 - Otherwise:
- All file will be stored locally in a folder (to create automatically if not present):
- The relative path of this folder is given by the environment variable
FOLDER_PATH - If this variable is not present or empty, use
/tmp/files_manageras storing folder path
- The relative path of this folder is given by the environment variable
- Create a local path in the storing folder with filename a UUID
- Store the file in clear (reminder:
datacontains the Base64 of the file) in this local path - Add the new file document in the collection
fileswith these attributes:userId: ID of the owner document (owner from the authentication)name: same as the value receivedtype: same as the value receivedisPublic: same as the value receivedparentId: same as the value received - if not present: 0localPath: for atype=file|image, the absolute path to the file save in local
- Return the new file with a status code 201
- All file will be stored locally in a folder (to create automatically if not present):
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"f21fb953-16f9-46ed-8d9c-84c6450ec80f"}
bob@dylan:~$
bob@dylan:~$ curl -XPOST 0.0.0.0:5000/files -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" -H "Content-Type: application/json" -d '{ "name": "myText.txt", "type": "file", "data": "SGVsbG8gV2Vic3RhY2shCg==" }' ; echo ""
{"id":"5f1e879ec7ba06511e683b22","userId":"5f1e7cda04a394508232559d","name":"myText.txt","type":"file","isPublic":false,"parentId":0}
bob@dylan:~$
bob@dylan:~$ ls /tmp/files_manager/
2a1f4fc3-687b-491a-a3d2-5808a02942c9
bob@dylan:~$
bob@dylan:~$ cat /tmp/files_manager/2a1f4fc3-687b-491a-a3d2-5808a02942c9
Hello Webstack!
bob@dylan:~$
bob@dylan:~$ curl -XPOST 0.0.0.0:5000/files -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" -H "Content-Type: application/json" -d '{ "name": "images", "type": "folder" }' ; echo ""
{"id":"5f1e881cc7ba06511e683b23","userId":"5f1e7cda04a394508232559d","name":"images","type":"folder","isPublic":false,"parentId":0}
bob@dylan:~$
bob@dylan:~$ cat image_upload.py
import base64
import requests
import sys
file_path = sys.argv[1]
file_name = file_path.split('/')[-1]
file_encoded = None
with open(file_path, "rb") as image_file:
file_encoded = base64.b64encode(image_file.read()).decode('utf-8')
r_json = { 'name': file_name, 'type': 'image', 'isPublic': True, 'data': file_encoded, 'parentId': sys.argv[3] }
r_headers = { 'X-Token': sys.argv[2] }
r = requests.post("http://0.0.0.0:5000/files", json=r_json, headers=r_headers)
print(r.json())
bob@dylan:~$
bob@dylan:~$ python image_upload.py image.png f21fb953-16f9-46ed-8d9c-84c6450ec80f 5f1e881cc7ba06511e683b23
{'id': '5f1e8896c7ba06511e683b25', 'userId': '5f1e7cda04a394508232559d', 'name': 'image.png', 'type': 'image', 'isPublic': True, 'parentId': '5f1e881cc7ba06511e683b23'}
bob@dylan:~$
bob@dylan:~$ echo 'db.files.find()' | mongo files_manager
{ "_id" : ObjectId("5f1e881cc7ba06511e683b23"), "userId" : ObjectId("5f1e7cda04a394508232559d"), "name" : "images", "type" : "folder", "parentId" : "0" }
{ "_id" : ObjectId("5f1e879ec7ba06511e683b22"), "userId" : ObjectId("5f1e7cda04a394508232559d"), "name" : "myText.txt", "type" : "file", "parentId" : "0", "isPublic" : false, "localPath" : "/tmp/files_manager/2a1f4fc3-687b-491a-a3d2-5808a02942c9" }
{ "_id" : ObjectId("5f1e8896c7ba06511e683b25"), "userId" : ObjectId("5f1e7cda04a394508232559d"), "name" : "image.png", "type" : "image", "parentId" : ObjectId("5f1e881cc7ba06511e683b23"), "isPublic" : true, "localPath" : "/tmp/files_manager/51997b88-5c42-42c2-901e-e7f4e71bdc47" }
bob@dylan:~$
bob@dylan:~$ ls /tmp/files_manager/
2a1f4fc3-687b-491a-a3d2-5808a02942c9 51997b88-5c42-42c2-901e-e7f4e71bdc47
bob@dylan:~$
In the file routes/index.js, add 2 new endpoints:
GET /files/:id=>FilesController.getShowGET /files=>FilesController.getIndex
In the file controllers/FilesController.js, add the 2 new endpoints:
GET /files/:id should retrieve the file document based on the ID:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401
- If not found, return an error
- If no file document is linked to the user and the ID passed as parameter, return an error
Not foundwith a status code 404 - Otherwise, return the file document
GET /files should retrieve all users file documents for a specific parentId and with pagination:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401
- If not found, return an error
- Based on the query parameters
parentIdandpage, return the list of file documentparentId:- No validation of
parentIdneeded - if theparentIdis not linked to any user folder, returns an empty list - By default,
parentIdis equal to 0 = the root
- No validation of
- Pagination:
- Each page should be 20 items max
pagequery parameter starts at 0 for the first page. If equals to 1, it means it’s the second page (form the 20th to the 40th), etc…- Pagination can be done directly by the
aggregateof MongoDB
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"f21fb953-16f9-46ed-8d9c-84c6450ec80f"}
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
[{"id":"5f1e879ec7ba06511e683b22","userId":"5f1e7cda04a394508232559d","name":"myText.txt","type":"file","isPublic":false,"parentId":0},{"id":"5f1e881cc7ba06511e683b23","userId":"5f1e7cda04a394508232559d","name":"images","type":"folder","isPublic":false,"parentId":0},{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":true,"parentId":"5f1e881cc7ba06511e683b23"}]
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files?parentId=5f1e881cc7ba06511e683b23 -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
[{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":true,"parentId":"5f1e881cc7ba06511e683b23"}]
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25 -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":true,"parentId":"5f1e881cc7ba06511e683b23"}
bob@dylan:~$
In the file routes/index.js, add 2 new endpoints:
PUT /files/:id/publish=>FilesController.putPublishPUT /files/:id/publish=>FilesController.putUnpublish
In the file controllers/FilesController.js, add the 2 new endpoints:
PUT /files/:id/publish should set isPublic to true on the file document based on the ID:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401
- If not found, return an error
- If no file document is linked to the user and the ID passed as parameter, return an error
Not foundwith a status code 404 - Otherwise:
- Update the value of
isPublictotrue - And return the file document with a status code 200
- Update the value of
PUT /files/:id/unpublish should set isPublic to false on the file document based on the ID:
- Retrieve the user based on the token:
- If not found, return an error
Unauthorizedwith a status code 401
- If not found, return an error
- If no file document is linked to the user and the ID passed as parameter, return an error
Not foundwith a status code 404 - Otherwise:
- Update the value of
isPublictofalse - And return the file document with a status code 200
- Update the value of
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"f21fb953-16f9-46ed-8d9c-84c6450ec80f"}
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25 -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":false,"parentId":"5f1e881cc7ba06511e683b23"}
bob@dylan:~$
bob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/publish -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":true,"parentId":"5f1e881cc7ba06511e683b23"}
bob@dylan:~$
bob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/unpublish -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e8896c7ba06511e683b25","userId":"5f1e7cda04a394508232559d","name":"image.png","type":"image","isPublic":false,"parentId":"5f1e881cc7ba06511e683b23"}
bob@dylan:~$
In the file routes/index.js, add one new endpoint:
GET /files/:id/data=>FilesController.getFile
In the file controllers/FilesController.js, add the new endpoint:
GET /files/:id/data should return the content of the file document based on the ID:
- If no file document is linked to the ID passed as parameter, return an error
Not foundwith a status code 404 - If the file document (folder or file) is not public (
isPublic: false) and no user authenticate or not the owner of the file, return an errorNot foundwith a status code 404 - If the type of the file document is
folder, return an errorA folder doesn't have contentwith a status code 400 - If the file is not locally present, return an error
Not foundwith a status code 404 - Otherwise:
- By using the module
mime-types, get the MIME-type based on thenameof the file - Return the content of the file with the correct MIME-type
- By using the module
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"f21fb953-16f9-46ed-8d9c-84c6450ec80f"}
bob@dylan:~$
bob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/unpublish -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e879ec7ba06511e683b22","userId":"5f1e7cda04a394508232559d","name":"myText.txt","type":"file","isPublic":false,"parentId":0}
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
Hello Webstack!
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data ; echo ""
{"error":"Not found"}
bob@dylan:~$
bob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/publish -H "X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f" ; echo ""
{"id":"5f1e879ec7ba06511e683b22","userId":"5f1e7cda04a394508232559d","name":"myText.txt","type":"file","isPublic":true,"parentId":0}
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data ; echo ""
Hello Webstack!
bob@dylan:~$
Update the endpoint POST /files endpoint to start a background processing for generating thumbnails for a file of type image:
- Create a
BullqueuefileQueue - When a new image is stored (in local and in DB), add a job to this queue with the
userIdandfileId
Create a file worker.js:
- By using the module
Bull, create a queuefileQueue - Process this queue:
- If
fileIdis not present in the job, raise an errorMissing fileId - If
userIdis not present in the job, raise an errorMissing userId - If no document is found in DB based on the
fileIdanduserId, raise an errorFile not found - By using the module
image-thumbnail, generate 3 thumbnails withwidth= 500, 250 and 100 - store each result on the same location of the original file by appending_<width size>
- If
Update the endpoint GET /files/:id/data to accept a query parameter size:
sizecan be500,250or100- Based on
size, return the correct local file - If the local file doesn’t exist, return an error
Not foundwith a status code 404
Terminal 3: (start the worker)
bob@dylan:~$ npm run start-worker
...
Terminal 2:
bob@dylan:~$ curl 0.0.0.0:5000/connect -H "Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=" ; echo ""
{"token":"f21fb953-16f9-46ed-8d9c-84c6450ec80f"}
bob@dylan:~$
bob@dylan:~$ python image_upload.py image.png f21fb953-16f9-46ed-8d9c-84c6450ec80f 5f1e881cc7ba06511e683b23
{'id': '5f1e8896c7ba06511e683b25', 'userId': '5f1e7cda04a394508232559d', 'name': 'image.png', 'type': 'image', 'isPublic': True, 'parentId': '5f1e881cc7ba06511e683b23'}
bob@dylan:~$ ls /tmp/files_manager/
2a1f4fc3-687b-491a-a3d2-5808a02942c9 51997b88-5c42-42c2-901e-e7f4e71bdc47 6dc53397-8491-4b7c-8273-f748b1a031cb 6dc53397-8491-4b7c-8273-f748b1a031cb_100 6dc53397-8491-4b7c-8273-f748b1a031cb_250 6dc53397-8491-4b7c-8273-f748b1a031cb_500
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data -so new_image.png ; file new_image.png
new_image.png: PNG image data, 471 x 512, 8-bit/color RGBA, non-interlaced
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data?size=100 -so new_image.png ; file new_image.png
new_image.png: PNG image data, 100 x 109, 8-bit/color RGBA, non-interlaced
bob@dylan:~$
bob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data?size=250 -so new_image.png ; file new_image.png
new_image.png: PNG image data, 250 x 272, 8-bit/color RGBA, non-interlaced
bob@dylan:~$
Of course, a strong and stable project can not be good without tests.
Create tests for redisClient and dbClient.
Create tests for each endpoints:
GET /statusGET /statsPOST /usersGET /connectGET /disconnectGET /users/mePOST /filesGET /files/:idGET /files(don’t forget the pagination)PUT /files/:id/publishPUT /files/:id/unpublishGET /files/:id/data
Update the endpoint POST /users endpoint to start a background processing for sending a “Welcome email” to the user:
- Create a
BullqueueuserQueue - When a new user is stored (in DB), add a job to this queue with the
userId
Update the file worker.js:
- By using the module
Bull, create a queueuserQueue - Process this queue:
- If
userIdis not present in the job, raise an errorMissing userId - If no document is found in DB based on the
userId, raise an errorUser not found - Print in the console
Welcome <email>!
- If
In real life, you can use a third party service like Mailgun to send real email. These API are slow, (sending via SMTP is worst!) and sending emails via a background job is important to optimize API endpoint.