Route-Based Middleware to Handle Default Population Query Logic
Tags: strapi, route, headlesscms, javascript
Published: 2022-11-10 09:13:08

**Author: Kellen Bolger**

Learn how to set the default "populate" options via route middleware. Instead of passing "populate" on each request from the frontend, the can handle this functionality via route-based middleware in the backend. This will allow you to keep your frontend requests lean and organized.
 
You can also use this to control what data will be returned by default and not allow the user to add additional populate options in the frontend.
 
## What is Route Middleware?
In Strapi, Route Middleware has a more limited scope and is configured and used as middleware at the route level. You can [learn more in the Strapi documentation](https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#policies). Now, let's jump in and learn how to set this up.
 
## Sample Content Structure
In this example, I will use a simple blog post type consisting of a title, body, hero image, slug, and authors, which is a one-to-many relationship with a user. Each blog post can have many authors.
 
![sample content structure](https://camo.githubusercontent.com/f138cc8dc200ad473877f082d3ad8c445f1d48f08c88541e301c41b992353cd5/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f64786768747170616f2f696d6167652f75706c6f61642f76313636303037313036352f53637265656e5f53686f745f323032322d30382d30395f61745f322e35302e30385f504d5f7576726f6a672e706e67)
 
Before getting into how to implement custom middleware, let's look at why you might want to add middleware for this use case in the first place.
 
## The Problem
By default, population structure needs to be defined and sent on each `client-side` request, else the request will return only top-level parent content.
 
A `GET` request to `localhost:1337/api/blog-posts` returns the following:
 
```
// localhost:1337/api/blog-posts
{
 "data": [
   {
     "id": 1,
     "attributes": {
       "title": "Test Blog Post",
       "body": "Test blog content",
       "slug": "test-blog-post",
       "createdAt": "2022-08-09T18:45:19.012Z",
       "updatedAt": "2022-08-09T18:45:21.710Z",
       "publishedAt": "2022-08-09T18:45:21.707Z"
     }
   }
 ],
 "meta": {
   "pagination": {
     "page": 1,
     "pageSize": 25,
     "pageCount": 1,
     "total": 1
   }
 }
}
```
 
This is not ideal, seeing as important data has been excluded, such as the `heroImage` and `authors`' information.
 
## Populate = *
An easy solution to the problem above involves adding `populate=*` to the initial query.
 
`localhost:1337/api/blog-posts?populate=*` returns the following:
 
```js
// localhost:1337/api/blog-posts?populate=*
{
 "data": [
   {
     "id": 1,
     "attributes": {
       "title": "Test Blog Post",
       "body": "Test blog content",
       "slug": "test-blog-post",
       "createdAt": "2022-08-09T18:45:19.012Z",
       "updatedAt": "2022-08-09T19:22:39.637Z",
       "publishedAt": "2022-08-09T18:45:21.707Z",
       "heroImage": {
         "data": {
           "id": 1,
           "attributes": {
             "name": "test_cat.jpeg",
             "alternativeText": "test_cat.jpeg",
             "caption": "test_cat.jpeg",
             "width": 500,
             "height": 500,
             "formats": {
               "thumbnail": {
                 "name": "thumbnail_test_cat.jpeg",
                 "hash": "thumbnail_test_cat_2bdaa9fbe9",
                 "ext": ".jpeg",
                 "mime": "image/jpeg",
                 "path": null,
                 "width": 156,
                 "height": 156,
                 "size": 5.01,
                 "url": "/uploads/thumbnail_test_cat_2bdaa9fbe9.jpeg"
               }
             },
             "hash": "test_cat_2bdaa9fbe9",
             "ext": ".jpeg",
             "mime": "image/jpeg",
             "size": 21.78,
             "url": "/uploads/test_cat_2bdaa9fbe9.jpeg",
             "previewUrl": null,
             "provider": "local",
             "provider_metadata": null,
             "createdAt": "2022-08-09T19:06:25.220Z",
             "updatedAt": "2022-08-09T19:06:25.220Z"
           }
         }
       },
       "authors": {
         "data": {
           "id": 1,
           "attributes": {
             "username": "testUser",
             "email": "test@test.com",
             "provider": "local",
             "confirmed": true,
             "blocked": false,
             "createdAt": "2022-08-09T19:07:03.325Z",
             "updatedAt": "2022-08-09T19:07:03.325Z"
           }
         }
       }
     }
   }
 ],
 "meta": {
   "pagination": {
     "page": 1,
     "pageSize": 25,
     "pageCount": 1,
     "total": 1
   }
 }
}
```
 
While this does return *more* data, the main flaw with this approach is that you don't have control over *what* data is returned. You are still not receiving valuable information, such as the author's `role` while also receiving data you might not care about.
 
## Getting Granular
Instead of using `populate=*`, you can filter the query using [LHS Bracket syntax](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/api-parameters.html).
 
The query might look like this:
 
```
localhost:1337/api/blog-posts?populate[heroImage][fields]
[0]=name&populate[heroImage][fields]
[1]=alternativeText&populate[heroImage][fields]
[2]=caption&populate[heroImage][fields]
[3]=url&populate[authors][fields]
[0]=username&populate[authors][populate][role][fields]
[0]=name
```
 
While this correctly returns the data specified, it is not feasible to use. This query is quite unruly and certainly not something you'd want to consistently use throughout your application.
 
## Enter... Query-String
Using [query-string](https://www.npmjs.com/package/qs), we can implement the same query as above in a much more readable and reusable manner. The query can easily be used directly in the front-end of our application.
 
For example:
 
```js
const qs = require('qs')
const query = qs.stringify(
 {
   populate: {
     heroImage: {
       fields: ['name', 'alternativeText', 'caption', 'url'],
     },
     authors: {
       fields: ['username'],
       populate: {
         role: {
           fields: ['name'],
         },
       },
     },
   },
 },
 {
   encodeValuesOnly: true, // prettify URL
 }
)
// `localhost:1337/api/blog-posts?${query}`
```
 
It successfully returns the same result as the above query where we used bracket syntax:
```
{
 "data": [
   {
     "id": 1,
     "attributes": {
       "title": "Test Blog Post",
       "body": "Test blog content",
       "slug": "test-blog-post",
       "createdAt": "2022-08-09T18:45:19.012Z",
       "updatedAt": "2022-08-09T19:22:39.637Z",
       "publishedAt": "2022-08-09T18:45:21.707Z",
       "heroImage": {
         "data": {
           "id": 1,
           "attributes": {
             "name": "test_cat.jpeg",
             "alternativeText": "test_cat.jpeg",
             "caption": "test_cat.jpeg",
             "url": "/uploads/test_cat_2bdaa9fbe9.jpeg"
           }
         }
       },
       "authors": {
         "data": {
           "id": 1,
           "attributes": {
             "username": "testUser"
           }
         }
       }
     }
   }
 ],
 "meta": {
   "pagination": {
     "page": 1,
     "pageSize": 25,
     "pageCount": 1,
     "total": 1
   }
 }
}
```
> For many use cases, this will be the logical end. However, if you find that you are re-using the same query over and over again, read on.
 
## Query Logic in Middleware
Now that you know how to build useful queries, you can look at optimizing the process further by adding a query directly into route-based middleware.
 
### Initializing the New Middleware
In Strapi, you can generate boilerplate code directly from the CLI. In your terminal, run the command:
 
```bash
yarn strapi generate
```
 
From there, navigate to `middleware`.
 
![middleware](https://camo.githubusercontent.com/4d1ae1ae5760e28d759d98e27acf580c39c0cc0213ec03f6c6332f4fe6bd2f44/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f64786768747170616f2f696d6167652f75706c6f61642f76313636303136303835322f53637265656e5f53686f745f323032322d30382d31305f61745f332e34342e35335f504d5f61756879646f2e706e67)
 
You will be prompted to name the middleware. Then, you will need to select where you want to add this middleware.
 
![run middleware](https://camo.githubusercontent.com/d91c7ea8176390eab732316ae867bacc94f4802b97a9a1da18b6d2814f5a26d2/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f64786768747170616f2f696d6167652f75706c6f61642f76313636303136313235332f53637265656e5f53686f745f323032322d30382d31305f61745f332e35342e30305f504d5f6467387964342e706e67)
 
For this example, choose `Add middleware to an existing API` since you only want it to run on the blog-post route.
 
![select route](https://camo.githubusercontent.com/d9b871b068543d4707eed05a8b5f3681a067aa0bfa9fbc6bbe9d4a2ccb52f8c9/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f64786768747170616f2f696d6167652f75706c6f61642f76313636303136313336322f53637265656e5f53686f745f323032322d30382d31305f61745f332e35352e35325f504d5f69676e6263622e706e67)
 
Now in the Strapi project, if you navigate to `src > api > blog-post > middlewares > test.js`, you will see the following boilerplate:
 
```js
'use strict'
 
/**
* `test` middleware.
*/
 
module.exports = (config, { strapi }) => {
 // Add your own logic here.
 return async (ctx, next) => {
   strapi.log.info('In test middleware.')
 
   await next()
 }
}
```
### Enable Middleware on Route
Before utilizing the middleware, you first need to enable it on the route.
 
If you head to ```src > api > blog-post > routes > blog-post.js```, you'll see the default route configuration:
 
```js
'use strict'
 
/**
* blog-post router.
*/
 
const { createCoreRouter } = require('@strapi/strapi').factories
 
module.exports = createCoreRouter('api::blog-post.blog-post')
```
 
Edit this file as follows:
 
```js
'use strict'
 
/**
* blog-post router.
*/
 
const { createCoreRouter } = require('@strapi/strapi').factories
 
module.exports = createCoreRouter('api::blog-post.blog-post', {
 config: {
   find: {
     middlewares: ['api::blog-post.test'],
   },
 },
})
```
 
> **Pro Tip:** If you can't remember the internal UIDs of the middleware, which is `api::blog-post.test`, run the command below:
 
```bash
yarn strapi middlewares:list
```
 
This will give you a list of all internal middleware UIDs in your project
 
![list of middleware](https://camo.githubusercontent.com/9952de6b542361258829a4ef7b926b1241c5a471d03e6e237eb9270c3487f5d7/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f64786768747170616f2f696d6167652f75706c6f61642f76313636303136333135302f53637265656e5f53686f745f323032322d30382d31305f61745f342e31372e32395f504d5f6c306d68726f2e706e67)
 
To see all of the available customizations for core routes, [check out the docs](https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#configuring-core-routers).
 
## Adding Logic to the Middleware
Now that thw middleware has been initialized in your project and added to the `blog-post` route, it's time to add some logic.
 
The purpose of this middleware is so you do not need to build your query on the frontend to return the data you are looking to fetch.
 
By adding your logic directly to the middleware, all of the querying will happen automatically when you head to the `localhost:1337/api/blog-post` route.
 
Instead of writing your query on the frontend, add it directly to the middleware, as such:
 
```js
// src > api > blog-post > middlewares > test.js
 
module.exports = (config, { strapi }) => {
 // This is where we add our middleware logic
 return async (ctx, next) => {
   ctx.query.populate = {
     heroImage: {
       fields: ['name', 'alternativeText', 'caption', 'url'],
     },
     authors: {
       fields: ['username'],
       populate: {
         role: {
           fields: ['name'],
         },
       },
     },
   }
 
   await next()
 }
}
```
 
Now, stop the Strapi server and run `yarn strapi build` to rebuild your Strapi instance. Once the build is complete, run `yarn develop` to restart the Strapi server.
 
If you go to the route `localhost:1337/api/blog-posts` the response returns:
 
```js
{
 "data": [
   {
     "id": 1,
     "attributes": {
       "title": "Test Blog Post",
       "body": "Test blog content",
       "slug": "test-blog-post",
       "createdAt": "2022-08-09T18:45:19.012Z",
       "updatedAt": "2022-08-09T19:22:39.637Z",
       "publishedAt": "2022-08-09T18:45:21.707Z",
       "heroImage": {
         "data": {
           "id": 1,
           "attributes": {
             "name": "test_cat.jpeg",
             "alternativeText": "test_cat.jpeg",
             "caption": "test_cat.jpeg",
             "url": "/uploads/test_cat_2bdaa9fbe9.jpeg"
           }
         }
       },
       "authors": {
         "data": {
           "id": 1,
           "attributes": {
             "username": "testUser"
           }
         }
       }
     }
   }
 ],
 "meta": {
   "pagination": {
     "page": 1,
     "pageSize": 25,
     "pageCount": 1,
     "total": 1
   }
 }
}
```
 
No query string is needed!
 
## Congrats on Making it to the End!
Here is a recap of what you've just learned:
- How to use `populate=*`.
- How to query and filter using [LHS Bracket syntax](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/api-parameters.html).
- How to use [query-string](https://www.npmjs.com/package/qs) to build custom client-side queries for better re-use.
- How to add custom middlewares to your project.
- How to implement middlewares on an api route.