I recently interviewed for a backend developer role at a startup involved in financial products, as a 2020 grad. The take-home assignment they had me submit had a few basic goals and they recommended the Nodejs/MongoDB stack. I received the following feedback on my submission:
1. Visibility of REST architecture is less.
2. Code structuring could have been better (I agree with this).
3. If the language chosen is Nodejs, at least the basic syntax usage should be correct. The opposite was observed.
My questions about this feedback are:
1. Isn't the structure of a REST API highly subjective? How can I make my application more compliant with REST goals?
2. What is "incorrect syntax usage?" If my syntax were incorrect, the project would misbehave or not work, wouldn't it? I posted this question on the r/codereview subreddit, and got little useful feedback apart from one comment saying "spaghetti." I'd love it if you could give me pointers on how to improve my code.
I'd love to learn from this exercise and am open to all feedback/criticism. My code resides at github.com/utkarshpant/portfolio-api along with the documentation. I tested the application using Postman and as an exercise, I am writing unit/integration tests for it.
I'm reproducing below a portion of the routes/portfolio_v2.js
file, where I have implemented the majority of the endpoints:
const dbDebugger = require('debug')('app:db');
const apiDebugger = require('debug')('app:api');
const express = require('express');
const router = express.Router();
const Joi = require('joi');
const customError = require('http-errors');
const errorHandlerMiddleware = require('../middleware/errorHandlerMiddleware');
// importing models;
const Portfolio = require('../models/portfolioModel');
// import validations;
const validations = require('../validations/validateRequest');
// get returns on portfolio
router.get('/getReturns/:portfolioName', errorHandlerMiddleware((req, res) => {
const portfolioName = req.params.portfolioName;
(async () => {
const portfolio = await Portfolio.findOne({ "name": portfolioName }).catch(err => res.send(error));
if (!portfolio) {
return res.status(404).send("No portfolio found");
}
const currentPrice = 100;
let returns = 0;
portfolio.securities.forEach(security => {
apiDebugger(`The returns on ${security.ticker} are ${((currentPrice - security.avgBuyPrice) * security.shares)}`);
returns += ((currentPrice - security.avgBuyPrice) * security.shares)
})
console.log("Returns:\t", returns);
res.send({
portfolio: portfolio.name,
cumulativeReturns: returns
});
})();
}));
// place a buy trade
router.post('/buy/:portfolioName', errorHandlerMiddleware((req, res) => {
/*
Request body includes:
Trade object, including ticker, type, quantity, price
TODO:
- validations for
- ticker match
- trade type == sell,
- shares - quantity > 0 always
- resultant shares > 0 always
*/
// validating request body;
const { error } = validations.validateTradeRequest(req);
if (error) {
throw customError(400, "Bad Request; re-check the request body.");
} else {
// mismatch of type;
if (req.body.type != "buy") {
throw customError(400, "Bad request; type must be 'buy'.")
}
const portfolioName = req.params.portfolioName;
const trade = req.body;
(async () => {
// retrieve portfolio and find relevant security;
const portfolio = await Portfolio.findOne({ "name": portfolioName }).catch(err => res.send(err));
if (!portfolio) {
return res.status(404).send("No portfolio found");
}
const security = portfolio.securities.find(security => security.ticker == trade.ticker);
// if the ticker does not exist, return a 404;
if (!security) {
return res.status(404).send("Invalid ticker.");
}
// register sell trade and update shares;
security.trades.push(trade);
let oldShares = security.shares;
security.shares += trade.quantity;
security.avgBuyPrice = (((security.avgBuyPrice) * (oldShares)) + ((trade.price) * (trade.quantity))) / (security.shares);
apiDebugger(`(security.avgBuyPrice): ${security.avgBuyPrice},\nsecurity.shares: ${security.shares},\ntrade.price: ${trade.price},\ntrade.quantity: ${trade.quantity}\n`);
// save portfolio
try {
await portfolio.save().then(res.status(200).send(trade));
} catch (err) {
let errorMessages = [];
ex.errors.forEach(property => errorMessages.push(property));
res.status(500).send("An error occured in saving the transaction.")
}
})();
}
}));
// place a sell trade
router.post('/sell/:portfolioName', errorHandlerMiddleware((req, res) => {
/*
Request body includes:
Trade object, including ticker, type, quantity
TODO:
- validations for
- ticker match
- trade type == sell,
- shares - quantity > 0 always
- resultant shares > 0 always
*/
// validating request body;
const { error } = validations.validateTradeRequest(req);
if (error) {
throw customError(400, "Bad Request; re-check the request body.");
} else {
if (req.body.type != "sell") {
throw customError(400, "Bad Request; type must be 'sell'.");
}
const portfolioName = req.params.portfolioName;
const trade = req.body;
(async () => {
// retrieve portfolio and find relevant security;
const portfolio = await Portfolio.findOne({ "name": portfolioName }).catch(err => res.send(err));
if (!portfolio) {
return res.status(404).send("No portfolio found");
}
const security = await portfolio.securities.find(security => security.ticker == trade.ticker);
// check that resultant share count > 0;
if ((security.shares - trade.quantity) < 0) {
// throw customError(422, `The given Trade will result in ${security.shares - trade.quantity} shares and cannot be processed.`);
return res.status(422).send(`Request cannot be serviced. Results in (${security.shares - trade.quantity}) shares.`);
}
// register sell trade and update shares;
security.trades.push({ "ticker": trade.ticker, "type": "sell", quantity: trade.quantity });
security.shares -= trade.quantity;
// save portfolio
try {
await portfolio.save().then(res.status(200).send(trade)).catch();
} catch (err) {
let errorMessages = [];
ex.errors.forEach(property => errorMessages.push(property));
res.status(500).send("An error occured in saving the transaction.")
}
})();
}
}));
function validateRequest(request) {
const tradeRequestSchema = Joi.object({
ticker: Joi.string().required().trim(),
type: Joi.string().required().valid("buy", "sell").trim(),
quantity: Joi.number().required().min(1).positive(),
price: Joi.number().min(1).positive()
})
return tradeRequestSchema.validate(request.body);
}
module.exports = router;
Thanks!