This week was all about the tiny things. Minuscule in volume but HUGE in importance. Let's get right into it!
Topics Covered✅
Last week, I was able to create a couple of backends. However, that was very basic stuff, where simply hitting different endpoints with different body content would yield results. In reality, however, it would be a bit different. The user could send various types of input(valid and invalid), and we would face errors and need some way to manage them.
The way to do all this is through-
- Global catches and middlewares
- Input validation
Both of these topics, I learnt and discovered through small assignments.
1a. Middlewares and Global Catches📎
Say I have one endpoint that needs to check for the username and password sent in the body, and match it with the real username and password that is stored locally for now. The layman's way to do this would be something like this
const username = req.headers.username;
const password = req.headers.password;
if (!(username === "nikhil" && password === "sharma")) {
res.status(400).json({
msg: "auth is wrong",
});
}
Now, say I have to replicate this for 5 more endpoints. I could simply copy-paste the code snippet into all endpoints, but then I'd be defying DRY- DON'T REPEAT YOURSELF, which is one of the most fundamental things in all of software development.
Then a better way to do this would be by using Middlewares. A middleware is just a function that you pass into an endpoint. A middleware for simplifying the above problem would look like this.
function userMiddleware(req,res,next){
const {username , password }= req.headers;
if (!(username === "nikhil" && password === "sharma")) {
return res.status(400).json({
msg: "auth is wrong",
});
}
else{
next();
}
}
This would then be passed into the endpoint like this
app.get("/health-checkup" , userMiddleware, (req,res)=>{
res.send("Healthy kidneys");
})
The middlewares always have three parameters - (req,res,next). I was familiar with req and res, but "next" was something new. 'next()' is what passes control from one middelware to the next (in case of multiple middlewares) or simply back to the endpoint.
1b. A special type of middleware is error-handling global catches
When our code randomly throws an error, it's possible that a lot of important and confidential backend information could get displayed to the user, which is not something we want. This is exactly where global catches come in.
If we put this -
app.use((err,req,res,next)=>{
console.log(err);
return res.status(500).json({"msg":"sorry our server is bad"});
})
at the end of our backend code, after all endpoints, then anytime a random error gets thrown from any endpoint, it gets caught in this global catch, where we can display whatever we want to the user.
That way, we do two things
- We don't leak any private information
- We can log the error internally for the dev team to look at.
Here, the four parameters are what separate this particular middleware from the rest. The extra first parameter is what tells Express that this middleware is for handling errors.
The need for Input Validation
In real-life cases, it would be careless to trust the user to always send the exact type of input that you need. Various phishing attempts could be made by sending in malicious inputs. Thus, we need some means to "validate" the input that the user sends us before sending that into the database. One means to do this is using the ZOD library.
ZOD🚀
ZOD lets us define a schema, which is basically a blueprint of the kind of input we expect from our users.
A zod schema can be something simple like
const schema = zod.array(zod.number());
which means that my input must be an array of numbers, or it can be something as complicated like-
import { z } from "zod";
// Example: user registration form
const userSchema = z.object({
username: z.string()
.min(3, "Username must be at least 3 characters")
.max(20, "Username can't exceed 20 characters"),
password: z.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[0-9]/, "Password must contain at least one number"),
email: z.string().email("Invalid email format"),
age: z.number().int().positive().optional(), // optional field
role: z.enum(["user", "admin", "superadmin"]), // only these values allowed
address: z.object({ // nested object
street: z.string(),
city: z.string(),
postalCode: z.string().regex(/^\d{5}$/, "Must be 5 digits")
}),
hobbies: z.array(z.string().min(2)), // array of strings, each at least 2 chars
metadata: z.record(z.string(), z.any()), // any extra info as key-value pairs
}).strict(); // ensures no extra fields are allowed
This may seem overwhelming at first, but in reality it is just a simple chaining structure of rules that we can modify to our advantage. For more details, you may refer to the Official Documentation.
After defining our schema, in our endpoint(or middleware) we simply call schema.safeParse()
on our input body, which automatically checks the entire input according to the schema and — unlike parse()
— returns a special result object that contains a success boolean property indicating the outcome, and either the successfully parsed data or a ZodError object. This makes it ideal for handling validation without using try-catch blocks.
A complete example of this would be as follows-
const schema = zod.object({
userEmail:zod.email(),
password:zod.string().min(8),
country: zod.literal("IN").or(zod.literal("US"))
})
app.post("/", (req, res) => {
const userObj = req.body.userObj;
const response = schema.safeParse(userObj);
res.send({
response,
});
});
New things I learnt this week🔄
- Middlewares, global catches and how much their positioning in the code matters!
- Global catches are like nets that catch all errors and also help in fixing the cause of those errors by extensive logging.
- Middlewares shine when you need to enforce concerns like logging, authentication, rate-limiting, or parsing data—that would otherwise bloat every endpoint.
Wrapping up
While the topics that I covered this week were small, they are crucial and of immense importance when building production ready apps. If you have any questions or feedback, make sure to comment and let me know!
I'll be back next week with more. Until then, stay consistent!
Top comments (0)