here’s how i’ve done it, but sure there must be a better solution:
async findBySlug(ctx)
{
const { slug } = ctx.params;
const query = {
filters: { slug },
...ctx.query,
populate: '*' // in my case I populate all the related fields, you can list only ones that you need
}
const filterOut = ['createdBy', 'updatedBy']; //these are the fields that we need to sanitize
const services = await strapi.entityService.findMany("api::service.service", query);
let sanitizedEntity = await this.sanitizeOutput(services);
//this array will contain all keys before sanitize + all the populated fields
const beforeSanitizeKeys = Object.keys(services[0]);
// this array will contain all keys after sanitize function returns stripping us from relational data
const keysSanitized = Object.keys(sanitizedEntity[0]);
//find only keys that are both not present in sanitized and filteredOut arrays
// will return array of keys that are relational data
let difference = beforeSanitizeKeys.filter(x => {
if (!keysSanitized.includes(x) && !filterOut.includes(x))
return x;
});
//append elational data to the returned object
difference.map(item => {
sanitizedEntity[0][item] = services[0][item];
});
console.log(sanitizedEntity[0])
return this.transformResponse(sanitizedEntity[0]);
}
SOLUTION for response not returning relational fields (Strapi v4)
From the code originally posted (Thanks to @Paul_Brats kindly posted it) seems it needs to be replaced the
with:
const { sanitize } = require('@strapi/utils'); // added at the top of the controller file
const sanitizedEntity = await sanitize.contentAPI.output(post, schema);
ATTENTION: remember to enable permission for findBySlug under Strapi Admin > Settings > Roles > Public, to allow us access to our newly created endpoint.
Request to the endpoint
and… from where you’ll invoke the endpoint it will require the ?populate=* query param.
GET http://localhost:1337/api/posts/find-by-slug/my-first-post?populate=*
NOTE: differently than was originally posted I used /api/posts/find-by-slug/:slug instead /api/post/find-by-slug/:slug. Plural path for the content type instead of singular. Personal preferences.
I’m new to Strapi but I was wondering if you could have a look at the code below and check if it’s a good alternative?
Even though entityService does return the meta, you can still get it using super.find(ctx) and destructure meta. Then, you just add it as a param to this.transformResponse !
However, is there any way to do it without having to add the “find-by-slug” in the request? I mean, Ifeel having this extra layer in the URL is not particularly SEO firendly and make the fetch services more complex inside our app.
I am new to strapi and come from a BE environment using Java.
I initially came up with the solution to query the DB using the Query Engine API to resolve the entity’s ID using the slug and then pass on the returned ID to subsequently query the DB using the Entity Service API to return the item.
I am not sure how much of a performance impact having these two queries to the DB result in; but I opted for that route as I experienced that populate was having difficulties when working on the relations inside the Query Engine API.
Another approach I’m doing is I keep the Strapi implementation as it is and do the logic on the client. I first do the find request to see if there is an item with that slug and then load its populated details via findOne:
// client
const slimRestaurantsResponse = await axios.get(
`${cmsUrl}/api/restaurants`,
{
params: {
filters: { slug },
fields: ['id'],
},
},
);
// if 'slug' is unique, there can only be one or none
const slimRestaurant = slimRestaurantsResponse.data.data[0];
if (!slimRestaurant) {
// handle what is basically a 404 not found situation
}
const restaurantResponse = await axios.get(
`${cmsUrl}/api/restaurants/${slimRestaurant.id}`,
{
params: {
populate: { ... },
},
},
);
const restaurant = restaurantResponse.data.data;
// continue working with restaurant
Pro: No custom Strapi routes/controllers/services.
Con: More logic and slightly more network traffic at the client.
Wanna quickly point out that @paulkuhle solution isn’t working on Strapi v4.8.0
Paul’s solutions helped me a lot. This was the code I’m currently using:
in src/index.js
'use strict';
/** this is a test */
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extensionService = strapi.plugin("graphql").service("extension");
const extension = () => ({
typeDefs: `
type Query {
faqQuestion(slug: String!): FaqQuestionEntityResponse
}
`,
resolvers: {
Query: {
faqQuestion: {
resolve: async (parent, args, context) => {
const { toEntityResponse } = strapi.service(
"plugin::graphql.format"
).returnTypes;
const data = await strapi.services["api::faq-question.faq-question"].find({
filters: { slug: args.slug },
});
const response = toEntityResponse(data.results[0]);
return response;
},
},
},
},
});
extensionService.use(extension);
},
/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap(/*{ strapi }*/) {},
};
In Strapi’s latest patch v2.8.0 the code isn’t working anymore because ID is now required when querying for singular content type requests:
Field \"faqQuestion\" argument \"id\" of type \"ID!\" is required, but it was not provided.
don’t know why they did it, but I’m looking for a solution because this breaks my whole app…