Don't trust your client!
With API development, you need to remember some principles. A fundamental principle is data protection.
Don't trust your client!
Yes, you heard me right, but what if I am building the client and the server? Nope, you still can't trust the client. When working on multi-tier applications, you have a client that sits on someone else's computer or on the cloud, and it must send and request data from your server. Sometimes the client and server are located in the same application; sometimes, the client is on a desktop, tablet, or phone. Regardless of the location, that client is going to request and send data to your API.
As an API Developer, your API call is a boundary between external clients and your business logic. You must protect borders within your application; by preserving the API boundary, you can mitigate challenges and potential errors that may surface. The act of protecting your API is essential. Also, it is not all about security, APIs should be secure, but you as an API developer should create an endpoint and qualify the data coming in or going out.
⚡ Receiving data in an API ⚡
When a request sends the API some data in a "query string" or "request body," it is an authenticated client who has the right to invoke this API method. As an API Developer, you should validate the data and make sure it contains the correct types and meets your API input rules.
There are several ways to approach this challenge like using GraphQL with TypeScript to validate types. I prefer using a validation library like Zod or writing a little validation library. In this article, we will show how you can use Zod.
Your data will come via a REST API in the form of params, query, and body objects in the Express Framework. The params object will contain any path parameters; these parameters reside in the URL path. For example, /data/:name/:id
in this case, name
and id
are provided to your handler function in the "request.params" object. The "request.query" object contains data from the query-string. For example, "/data/movies?limit=1", your query object will contain the limit property. { limit: '1'}. The "request.body" contains a JSON object when your client includes a JSON document in their request.
curl -X http://localhost:3000/api/movies \
-H 'Content-Type: application/json' \
-d '{"title": "Ghostbusters", "year": "1984"}'
To handle the data in the body property, you need a body-parser for your request. The Express BodyParser middleware "express.json" is a great way to handle processing the body data in a Request Object.
To handle the above request, my handler would look like the this:
app.post('/api/movies', express.json(), (request, response) => {
console.log(request.body)
response.json(request.body) // echo...
})
This example, just echos the body back to the caller. Using the middleware bodyparser express.json
we can get the body from the request object.
If you need to get the body of multiple requests, you can use theuse
method on theapp
object to set the middleware for entire application.app.use(express.json())
⚡ Validate your incoming data with zod ⚡
Zod is an open-source validation library; there are other validation libraries like Joi and Yup. The nice thing about Zod is that you have a lot of flexibility in validating your data.
Installing zod
npm install zod@next
Create a schema
const z = require('zod')
const schema = z.object({
id: z.string().max(50),
type: z.enum(['movie']),
title: z.string().max(100),
year: z.string().min(4).max(4)
})
Our movie schema requires, id, type, title and year.
Validate our schema during the request.
const createMovie = (title, year) =>
({
id: uuid.v4(), // generate id
type: 'movie',
title,
year
})
app.post('/api/movies', express.json, (request, response) => {
const movie = createMovie(request.body.title, request.body.year)
const result = schema.safeParse(movie)
if (result.success) {
response.json(doSave(result.data))
} else {
response.status(422).json(result.error)
// 422 Unproccesable entity
}
})
Using safeParse
we will get an object that contains a success property, if that property is true, then we will get a data property, if that property is false, we will get an error property. With the success property we can make a decision on how to move forward with our processing, if success is false, we can return an error to the caller, letting them know that the data is not valid.
⚡There is so much more you can do with zod ⚡
This is an example of how you can validate incoming data with zod, but zod is full of great features that you can use to increase the safety of the data being used in your application. You can check out the documentation here: https://github.com/colinhacks/zod/tree/v3
⚡ Summary ⚡
Regardless whether you use Zod or build your own validation libraries, the most important point is to validate your data coming in from the API. This a border in your application and it should be protected. The more you direct clients to the exact information your application needs the less chance of bad data getting in to your data store.