Outsourcing all the filtering, ordering, etc. to the back-end

22 June 2023

I’m the strong believer that front-end is for the user only, and that it should do the minimum. Calculations, filtering, sorting, mapping, and all of that sort of stuff should be done on the back-end.

My opinion is that if you’re fetching the data, e.g. a list of products from the api, and then you instantly do .sort() or .filter() or send another request based on the results of one of the previous ones, then something isn’t right.

The other day I was doing a GROQ query, where I was fetching a page that has a category field, that contains a reference to a category of articles that we want to render on the page

// QUERY_PAGE *[_type == "page" && slug.current == $slug] { _id, title, // ... other page fields category -> { _id, title, slug, }, }

But this query only gives us the category, it doesn’t provide the articles that have been tagged with it. Once we know the category, we can send another query to fetch articles that have this category tag, e.g.

// QUERY_ARTICLES *[ _type == "article" && category in category[]->slug.current] { // ... article fields },

And what I often see in the code is 2 API requests, one going after another, e.g.

const [page] = await sanityClient.fetch<SanityPage[]>(QUERY_PAGE); const articles = await sanityClient.fetch<SanityArticle[]>(QUERY_ARTICLES, { category: page.category.slug.current });

Which isn’t great, because we now have more lines of code and multiple API calls both of which we can potentially avoid.

The way we can avoid this is by accessing the category directly in the query, and let query handle that, e.g.

*[_type == "page" && slug.current == $slug] { _id, title, // ... other page fields category -> { _id, title, slug, "articles": *[ _type == "article" && ^.slug.current in category[]->slug.current] { // ... other article fields }, }, }

See that ^ character? That’s a reference to parent, i.e. category -> {}.

Now, we only need to send one request, and get everything we want.

But this is GROQ. What about others?

With GraphQL, you’d need to create a resolver that would fetch everything you need for you. So on the front-end you’d send

query GetPage { pages { _id title // ... other page fields category { _id title, slug, articles { // ... other articles field } } } }

And on the back, you’ll have a resolver that would fetch, firstly, the page fields, including category, and then make another 2nd call to get articles. But then returns all the fields to the front-end once and neatly structured.

When it comes to REST API, you have similar approach. You don’t try to fetch all the items from the front-end. It’s better if you communicate what you need to the back-end once, and then your back-end service gets that for you. Simple. E.g.

const pageData = await axios.get('/api/page/{id}')

KFESS. Keep front-end simple, stupid.

PS That was a reference to KISS. 😁