Parthiban
Parthiban

Reputation: 153

How to get random records from Strapi content API

I have records in strapi. I am using strapi content API. In my front-end, I need to display only 2 records randomly. For limiting, I have used limit query from content API. But random fetching what keyword I need to use. The official documentation doesn't provide any details regarding this - https://strapi.io/documentation/v3.x/content-api/parameters.html#available-operators

Upvotes: 8

Views: 5517

Answers (6)

Kalnode
Kalnode

Reputation: 11306

Randomize at Data Layer

There's no official Strapi API feature for random. You have to implement yourself, likely in the form of a Strapi service.

Please scrutinize all posted solutions. Also, with Strapi v4 and v5, AFAIK you still have to handle randomization yourself but there are newer features that may make this all easier.

Low-level, data layer solution

Below is what I've done previously, using Strapi v3. It affects the data query directly, but therefore it negates Strapi abstraction (the point of using Strapi). For the project, I had other reasons to make a raw query.

Alternatively, other posted solutions use the HTTP API, which has benefits too, but rely on network requests.

1 - Make a service function

File: api/mymodel/services/mymodel.js

This will contain our actual random query (SQL), and wrapping it in a service is handy because it can be used in many places (cron jobs, inside other models, etc).

module.exports = {
    serviceGetRandom() {

        return new Promise( (resolve, reject) => {
            
            // There's a few ways to query data.
            // This example uses Knex.

            const knex = strapi.connections.default
            let query = knex('mydatatable')

            // Add more .select()'s if you want other fields
            query.select('id')

            // RANDOMIZATION HERE: These rules enable us to get one random post
            query.orderByRaw('RAND()')
            query.limit(1)

            // Initiate the query and do stuff
            query
            .then(record => {
                console.log("getRandom() record: %O", record[0])
                resolve(record[0])
            })
            .catch(error => {
                reject(error)
            })
        })
    }
}

2 - Use the service somewhere, like a controller:

File: api/mymodel/controllers/mymodel.js

module.exports = {

    //(untested)

    getRandom: async (ctx) => {

        await strapi.services.mymodel.serviceGetRandom()
        .then(output => {
            console.log("getRandom output is %O", output.id)
            ctx.send({
                randomPost: output
            }, 200)
        })
        .catch( () => {
            ctx.send({
                message: 'Oops! Some error message'
            }, 204) // Place a proper error code here
        })

    }

}

3 - Create a route that points to this controller

File: api/mymodel/config/routes.json

...
    {
        "method": "GET",
        "path": "/mymodelrandom",
        "handler": "mymodel.getRandom",
        "config": {
            "policies": []
        }
    },
...

4 - In your front-end, access the route

(However you access your API)

e.g. ajax call to /api/mymodelrandom

Upvotes: 5

antokhio
antokhio

Reputation: 1934

Using Postgres and knex:

let { rows } = await strapi.db.connection.raw(
    `select id from posts where published_at IS NOT null order by random() limit ${count};`
);

how to make raw sql query on strapi v4 typescript

Upvotes: 0

atazmin
atazmin

Reputation: 5687

This seem to work for me with Strapi v.4 REST API

Controller, Get 6 random entries

"use strict";

/**
 *  artwork controller
 */

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

module.exports = createCoreController("api::artwork.artwork", ({ strapi }) => {
  const numberOfEntries = 6;
  return {
    async random(ctx) {
      const entries = await strapi.entityService.findMany(
        "api::artwork.artwork",
        {
          populate: ["image", "pageHeading", "seo", "socialMedia", "artist"],
        }
      );

      const randomEntries = [...entries].sort(() => 0.5 - Math.random());
      ctx.body = randomEntries.slice(0, numberOfEntries);
    },
  };
});

Route random.js

"use strict";

module.exports = {
  routes: [
    {
      method: "GET",
      path: "/artwork/random",
      handler: "artwork.random",
      config: {
        auth: false,
      },
    },
  ],
};

API

http://localhost:1337/api/artwork/random

enter image description here enter image description here

To match default data structure of Strapi

"use strict";

/**
 *  artwork controller
 */

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

module.exports = createCoreController("api::artwork.artwork", ({ strapi }) => {
  const numberOfEntries = 6;
  return {
    async random(ctx) {
      const entries = await strapi.entityService.findMany(
        "api::artwork.artwork",
        {
          populate: ["image", "pageHeading", "seo", "socialMedia", "artist"],
        }
      );

      const randomEntries = [...entries]
        .sort(() => 0.5 - Math.random())
        .slice(0, numberOfEntries);

      const structureRandomEntries = {
        data: randomEntries.map((entry) => {
          return {
            id: entry.id,
            attributes: entry,
          };
        }),
      };

      ctx.body = structureRandomEntries;
    },
  };
});

enter image description here

There is also a random sort plugin. https://www.npmjs.com/package/strapi-plugin-random-sort

Upvotes: 4

atazmin
atazmin

Reputation: 5687

This seem to work for me with Strapi v4.3.8 and graphql

src/index.js

"use strict";

module.exports = {

  register({ strapi }) {
    const extensionService = strapi.service("plugin::graphql.extension");

    const extension = ({ strapi }) => ({
      typeDefs: `
        type Query {
          randomTestimonial: Testimonial
        }
      `,
      resolvers: {
        Query: {
          randomTestimonial: async (parent, args) => {
            const entries = await strapi.entityService.findMany(
              "api::testimonial.testimonial"
            );
            const sanitizedRandomEntry =
              entries[Math.floor(Math.random() * entries.length)];

            return sanitizedRandomEntry;
          },
        },
      },
      resolversConfig: {
        "Query.randomTestimonial": {
          auth: false,
        },
      },
    });

    extensionService.use(extension);
  },
  bootstrap({ strapi }) {},
};

graphql query:

  query GetRandomTestimonial {
    randomTestimonial {
      __typename
      name
      position
      location
      description
    }
  }

generate random testimonial on route change/refresh https://jungspooner.com/biography

Upvotes: 1

Bruno Francisco
Bruno Francisco

Reputation: 4220

One way you can do this reliably is by two steps:

  1. Get the total number of records
  2. Fetch the number of records using _start and _limit parameters
// Untested code but you get the idea

// Returns a random number between min (inclusive) and max (exclusive)
function getRandomArbitrary(min, max) {
    return Math.random() * (max - min) + min;
}

const { data: totalNumberPosts } = await axios.get('/posts/count');

// Fetch 20 posts
const _limit = 20;

// We need to be sure that we are not fetching less than 20 posts
// e.g. we only have 40 posts. We generate a random number that is 30.
// then we would start on 30 and would only fetch 10 posts (because we only have 40)
const _start = getRandomArbitrary(0, totalNumberPosts - _limit);

const { data: randomPosts } = await axios.get('/posts', { params: { _limit, _start } })

The problem with this approach is that it requires two network requests but for my needs, this is not a problem.

Upvotes: 2

Cem Kaan
Cem Kaan

Reputation: 2226

There is no API parameter for getting a random result.

So: FrontEnd is the recommended solution for your question.

You need to create a random request range and then get some random item from this range.

function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}
const firstID = getRandomInt(restaurants.length);
const secondID = getRandomInt(3);
const query = qs.stringify({
  id_in:[firstID,secondID ] 
});
// request query should be something like GET /restaurants?id_in=3&id_in=6

Upvotes: 3

Related Questions