Strapi V4 search by slug instead ID

I was playing around with this, here is another way that worked for me.

GitHub Link Example

1 Override existing route to use slug vs id

src/api/post/routes/post.js

"use strict";

/**
 * post router.
 */

const { createCoreRouter } = require("@strapi/strapi").factories;
const defaultRouter = createCoreRouter("api::post.post");

// function to add to or override default router methods
const customRouter = (innerRouter, routeOveride = [], extraRoutes = []) => {
  let routes;

  return {
    get prefix() {
      return innerRouter.prefix;
    },
    get routes() {
      if (!routes) routes = innerRouter.routes;

      const newRoutes = routes.map((route) => {
        let found = false;

        routeOveride.forEach((overide) => {
          if (
            route.handler === overide.handler &&
            route.method === overide.method
          ) {
            found = overide;
          }
        });

        return found || route;
      });

      return newRoutes.concat(extraRoutes);
    },
  };
};

// Overide the default router with the custom router to use slug.
const myOverideRoutes = [
  {
    method: "GET",
    path: "/posts/:slug",
    handler: "api::post.post.findOne",
  },
];

const myExtraRoutes = [];

module.exports = customRouter(defaultRouter, myOverideRoutes, myExtraRoutes);


2 Override existing controller to use slug instead of id

src/api/post/controllers/post.js

"use strict";

/**
 *  post controller
 */

const { createCoreController } = require("@strapi/strapi").factories;

module.exports = createCoreController("api::post.post", ({ strapi }) => ({
  async findOne(ctx) {
    const { slug } = ctx.params;

    const query = {
      filters: { slug },
      ...ctx.query,
    };

    const post = await strapi.entityService.findMany("api::post.post", query);

    const sanitizedEntity = await this.sanitizeOutput(post);

    return this.transformResponse(sanitizedEntity[0]);
  },
}));

query

http://localhost:1337/api/posts/how-to-use-a-slug-vs-an-id-field

response

{
"data": {
"id": 1,
"attributes": {
"title": "How to use a slug vs an id field",
"content": "## My content",
"slug": "how-to-use-a-slug-vs-an-id-field",
"createdAt": "2022-06-16T21:20:05.209Z",
"updatedAt": "2022-06-16T23:32:45.288Z",
"publishedAt": "2022-06-16T21:20:08.070Z"
}
},
"meta": {}
}

query

http://localhost:1337/api/posts/how-to-use-a-slug-vs-an-id-field?populate=*

response

{
"data": {
"id": 1,
"attributes": {
"title": "How to use a slug vs an id field",
"content": "## My content",
"slug": "how-to-use-a-slug-vs-an-id-field",
"createdAt": "2022-06-16T21:20:05.209Z",
"updatedAt": "2022-06-16T23:32:45.288Z",
"publishedAt": "2022-06-16T21:20:08.070Z",
"image": {
"data": {
"id": 1,
"attributes": {
"name": "cat2.jpg",
"alternativeText": "cat2.jpg",
"caption": "cat2.jpg",
"width": 2392,
"height": 2500,
"formats": {
"thumbnail": {
"name": "thumbnail_cat2.jpg",
"hash": "thumbnail_cat2_bf2dde0b7d",
"ext": ".jpg",
"mime": "image/jpeg",
"path": null,
"width": 150,
"height": 156,
"size": 4.66,
"url": "/uploads/thumbnail_cat2_bf2dde0b7d.jpg"
},
"large": {
"name": "large_cat2.jpg",
"hash": "large_cat2_bf2dde0b7d",
"ext": ".jpg",
"mime": "image/jpeg",
"path": null,
"width": 957,
"height": 1000,
"size": 120.28,
"url": "/uploads/large_cat2_bf2dde0b7d.jpg"
},
"medium": {
"name": "medium_cat2.jpg",
"hash": "medium_cat2_bf2dde0b7d",
"ext": ".jpg",
"mime": "image/jpeg",
"path": null,
"width": 718,
"height": 750,
"size": 70.24,
"url": "/uploads/medium_cat2_bf2dde0b7d.jpg"
},
"small": {
"name": "small_cat2.jpg",
"hash": "small_cat2_bf2dde0b7d",
"ext": ".jpg",
"mime": "image/jpeg",
"path": null,
"width": 478,
"height": 500,
"size": 32.39,
"url": "/uploads/small_cat2_bf2dde0b7d.jpg"
}
},
"hash": "cat2_bf2dde0b7d",
"ext": ".jpg",
"mime": "image/jpeg",
"size": 544.61,
"url": "/uploads/cat2_bf2dde0b7d.jpg",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2022-06-16T23:32:42.135Z",
"updatedAt": "2022-06-16T23:32:42.135Z"
}
}
}
}
},
"meta": {}
}
4 Likes