Your task is to implement a simple CRUD API for a Product Catalog using an in-memory database underneath. Use Fastify as the framework.
- Task can be implemented in JavaScript or TypeScript
- Use Fastify as the web framework
- Only
fastify,@fastify/*plugins,nodemon,dotenv,cross-env,typescript,ts-node,ts-node-dev,tsx,zodandzod-related linter and its plugins, bundler and its plugins and loaders, formatter and its plugins,uuid,@types/*as well as libraries used for testing are allowed - Use 24.x.x version (24.10.0 or upper) of Node.js
- Prefer asynchronous API whenever possible
-
Implemented endpoint
api/products:- GET
api/productsis used to get all products- Server should answer with
status code200 and all product records
- Server should answer with
- GET
api/products/{productId}- Server should answer with
status code200 and the record withid === productIdif it exists - Server should answer with
status code400 and corresponding message ifproductIdis invalid (notuuid) - Server should answer with
status code404 and corresponding message if record withid === productIddoesn't exist
- Server should answer with
- POST
api/productsis used to create a record about a new product and store it in the database- Server should answer with
status code201 and newly created record - Server should answer with
status code400 and corresponding message if requestbodydoes not contain required fields or ifpriceis not a positive number
- Server should answer with
- PUT
api/products/{productId}is used to update an existing product- Server should answer with
status code200 and the updated record - Server should answer with
status code400 and corresponding message ifproductIdis invalid (notuuid) - Server should answer with
status code404 and corresponding message if record withid === productIddoesn't exist
- Server should answer with
- DELETE
api/products/{productId}is used to delete an existing product from the database- Server should answer with
status code204 if the record is found and deleted - Server should answer with
status code400 and corresponding message ifproductIdis invalid (notuuid) - Server should answer with
status code404 and corresponding message if record withid === productIddoesn't exist
- Server should answer with
- GET
-
Products are stored as
objectsthat have the following properties:id— unique identifier (string,uuid) generated on the server sidename— product name (string, required)description— product description (string, required)price— product price (number, required, must be > 0)category— product category (string, required, e.g."electronics","books","clothing")inStock— whether the product is in stock (boolean, required)
-
Requests to non-existing endpoints (e.g.
/some-non/existing/resource) should be handled (server should answer withstatus code404 and corresponding human-friendly message) -
Errors on the server side that occur during the processing of a request should be handled and processed correctly (server should answer with
status code500 and corresponding human-friendly message) -
Value of
porton which the application is running should be stored in.envfile
-
Important: The
.envfile itself should not be committed to the repository as it is considered a security bad practice. Please consider adding the.envfile to.gitignore.-
Instead, create and commit an
.env.examplefile that contains a list of required environment variables with reasonable default values -
Example of
.env.examplecontents:PORT=4000
-
-
There should be 2 modes of running the application (development and production):
- The application is run in development mode using
nodemonorts-node-devortsx(there is annpmscriptstart:dev) - The application is run in production mode (there is an
npmscriptstart:prodthat starts the build process and then runs the bundled file)
- The application is run in development mode using
-
There could be some tests for the API (not less than 3 scenarios). Example of a test scenario:
- Get all records with a
GETapi/productsrequest (an empty array is expected) - A new object is created by a
POSTapi/productsrequest (a response containing the newly created record is expected) - With a
GETapi/products/{productId}request, we try to get the created record by itsid(the created record is expected) - We try to update the created record with a
PUTapi/products/{productId}request (a response is expected containing an updated object with the sameid) - With a
DELETEapi/products/{productId}request, we delete the created object byid(confirmation of successful deletion is expected) - With a
GETapi/products/{productId}request, we are trying to get the deleted object byid(expected answer is that there is no such object)
- Get all records with a
-
There could be implemented horizontal scaling for the application. There should be an
npmscriptstart:multithat starts multiple instances of your application using the Node.jsClusterAPI (equal to the number of available parallelism - 1 on the host machine, each listening on port PORT + n) with a load balancer that distributes requests across them (using Round-robin algorithm). For example: available parallelism is 4,PORTis 4000. On runnpm run start:multiit works the following way:
- On
localhost:4000/apithe load balancer is listening for requests - On
localhost:4001/api,localhost:4002/api,localhost:4003/apiworkers are listening for requests from the load balancer - When user sends a request to
localhost:4000/api, the load balancer sends this request tolocalhost:4001/api, the next user request is sent tolocalhost:4002/apiand so on - After sending a request to
localhost:4003/api, the load balancer starts from the first worker again (sends request tolocalhost:4001/api) - State of the db should be consistent between different workers, for example:
- First
POSTrequest addressed tolocalhost:4001/apicreates a product - Second
GETrequest addressed tolocalhost:4002/apishould return the created product - Third
DELETErequest addressed tolocalhost:4003/apideletes the created product - Fourth
GETrequest addressed tolocalhost:4001/apishould return 404 status code for the created product
- First
- To generate all entities
ids use Node.js randomUUID