Supercharge Feeds with Algolia & GraphQL
Tags: stream, graphql, apollo, algolia
Published: 2020-12-11 18:05:24

---
title: Supercharge Feeds with Algolia & GraphQL
published: true
description:
canonical_url: https://getstream.io/blog/supercharge-feeds-with-algolia-and-graphql/ 
tags: stream,graphql,apollo,algolia 
cover_image: https://stream-blog-v2.imgix.net/blog/wp-content/uploads/a6bb0904ab9f94b459bff81ffeed8c93/og_gql-1.png?auto=compress%2Cformat&ixlib=php-3.3.0
---

When you're modeling and building an API, storing ID references to other documents to create relationships between types of data within your domain is paramount to efficient querying, scalability, and even your own sanity.

Imagine you're building the backend for a blog platform and want users to be able to search blog posts by category. So you store the category `name` on your posts, and you can query for "all posts with a category field equal to 'Tutorials'." But what if you wanted to eventually change "Tutorials" to "Walkthroughs"? 

You would have to update not only the category data but every post within the category too. If you instead store an ID for the category, the name — or any other property — can change, and the ID will still reference the correct object and keep the relationship alive.

Most service providers for databases and API-driven SaaS products provide a mechanism to make this easy for developers. [Stream Feeds](https://getstream.io/activity-feeds/) has enrichment for [users](https://getstream.io/docs/users_introduction/?language=js) and [collections](https://getstream.io/docs/collections_introduction/?language=js) in your [Activities](https://getstream.io/docs/adding_activities/?language=js) and [MongoDB](https://www.mongodb.com/) allows you to store a ref via an `ObjectID` that points to a document in another collection. In both cases you can store a reference to another thing and have it "auto-populate" when you query for the data and the ID you stored is replaced with the full document.

However, MongoDB cannot auto-populate an ID for anything other than another MongoDB document *that is stored in the same database*. You can't reference the ID of a Stream activity and expect it to just show up. At least, not out-of-the-box. The same applies to Stream: We can only enrich data for IDs that reference other "Stream Objects" — like collections or users.

This is just a fact of life for a developer, and it's not even necessarily a bad thing. Most platforms work this way so they can remain efficient, keep as much control as possible in their customers' hands, and allow you as the developer the flexibility to use whatever combination of tools, services, and APIs you see fit.

But what if we could do better? What if we could *teach* a GraphQL API to understand *all* of our data, **regardless of its origin or implementation**, so that we can enrich data seamlessly and bidirectionally between the tools, services, and APIs that comprise our stack?

Sounds idealistic right? 

### What We'll Be Building

Funnily enough, building this kind of nested relationship is something that GraphQL is designed to handle. Today, I'm going to walk through a relatively quick and simple example of how you can use Stream Feeds with `@stream-io/graphql-feeds` (our new, early-release GraphQL toolset for Stream Feeds) and extend the provided types to produce functionality such as this: 

{% gist https://gist.github.com/LukeSmetham/5dd7ac8a7b9af75947b407858b0f4322 %}

As you can see, there is a new custom field `search` — ***on the Feed itself***  — that will utilize Algolia under the hood, take full advantage of everything Algolia offers for best-in-class search, but will ultimately return *actual Stream Activities* rather than a list of search results with references to activities, collection entries, or users.

> **Disclaimer**: In this tutorial, I aim only to introduce the concepts of relationships across different services with a GraphQL API as the middleman, and how `graphql-feeds` can be leveraged in this way. We are going to gloss over some details such as authentication, which in this case will not be production-ready. In later posts we will expand on this example, add a database to go beyond Stream Collections, implement better authentication, and so much more.

### Initial Setup
To get started, rather than step through the linting and babel setup and installing dependencies, I've created a template repository so you can get straight to coding with the same setup I'm using.

If you have the [GitHub CLI](https://cli.github.com/) installed (`brew install gh`)  you can run the following command:

{% gist https://gist.github.com/LukeSmetham/3e70fd689c77ac15c909f87e26aabbc9 %}

> If you don't have/want the GH CLI, you can clone the repo as normal, or create your own repo from the template on the GitHub website, and then clone to your local machine from there.

Once you have the repo on your computer, `cd` into it and run `yarn` (you will also need to run `git pull origin main` if you created using the GitHub CLI).

Lastly, create a `.env` file in the root and paste in the following:

{% gist https://gist.github.com/LukeSmetham/3b6021108bcfe2d1cce61ff3cd53c833 %}

This is everything we need to get up and running! You'll no doubt notice that `src/index.js` (our entry point) is an empty file. We'll be getting to this next — but first, let's get that `.env` filled out and make sure we have everything set up for Stream and Algolia.

### Stream

For [Stream](https://getstream.io/), this tutorial will be easiest when using a new/empty Feeds App. You can take the things you learned and apply them to your own existing projects later.

Go to the Stream Dashboard ([create an account](https://getstream.io/accounts/signup/) if you haven't already) and once you're logged in, click "Create App" in the top right.

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/c9c59650ca948c746d489df16d19afc2/Screenshot_2020-12-09_at_12.20.png?auto=compress%2Cformat&fit=scale)

Then, on the next screen, grab your Stream API Key, Secret, and App ID:

![Stream Dashboard](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/2659a712a33d7a34d1a9d781c58da6af/Screenshot_2020-12-09_at_12.23.png?auto=compress%2Cformat&fit=scale)

Add these to the correct spots in the `.env` file from above and we're pretty much ready to move over to Algolia! 

We just have one more quick task in the Stream Dashboard: Click the "Add Feed Group" button in the dashboard (visible in the above screenshot) and create a new Flat feed group called "posts".

### Algolia

Next, you'll need an [Algolia account](https://algolia.com). You can set up a free trial on their website and spin up an Algolia application in just a few seconds.

Once set up, you will be presented with the Algolia dashboard and a drawer on the right side with instructions to set up your search indexes etc. Click "Create Index" and enter `POSTS` for the name.

![Creating an Algolia Index](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/8e6a8023fd47c024d9e074bf09ad9a4e/image_3.png?auto=compress%2Cformat&fit=scale)

After clicking "Create", you will be redirected to the "index" screen. 

Algolia is now ready to integrate into our API; however, we'll take this opportunity to add some facets to our index so we don't have to come back here later on.

First, click "Configuration" in the tabbed toolbar at the top of the index management screen, and choose "facets" from the sidebar menu on the left beneath "Filters and Faceting".

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/7e07637f305c1404f5a98d18a8cef3be/image_4.png?auto=compress%2Cformat&fit=scale&h=1216&ixlib=php-3.3.0&w=1536&wpsize=1536x1536)

We will add a new facet for the following fields `feedID` `userID` and `feedSlug`. This data does not exist yet in your Algolia index, so you will see the following warning below:

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/e022cbcf2d4271c4a5405e92ec06b0d1/Screenshot_2020-12-09_at_12.51.47.png?auto=compress%2Cformat&fit=scale&h=258&ixlib=php-3.3.0&w=1536&wpsize=1536x1536)

> This can be safely ignored as we will be adding objects containing these fields very shortly.

Now, make sure all of the facets are set to "filter only" by selecting it from the dropdown for each list item, and once done, click "Review and Save Settings".

Lastly, we need to get our **Algolia Admin Key** and **App ID** from the "API Keys" page and add them to our `.env`:

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/198103c247ce35a48dbdb85e63e6b096/image_5.png?auto=compress%2Cformat&fit=scale&h=1216&ixlib=php-3.3.0&w=1536&wpsize=1536x1536)

Now we can finally start to write some code! 🤓

### Initialize the API

Now that we have everything set up for our environment, we can start to implement `apollo-server`. The concepts from here on out apply to pretty much all JS GraphQL server libraries, and if you're using something like `apollo-server-express`, the setup will be pretty much identical. 

> **Disclaimer**: We will not be implementing authentication in this example and instead just passing the Stream token along as a means of identifying the user. In subsequent articles, we will learn to stitch in MongoDB and create custom authentication and user management.

Let's create a new file, inside of a new directory, in the `src` folder:  `src/schema/index.js`

{% gist https://gist.github.com/LukeSmetham/418770346a3204bf1eb93a5795f5f503 %}

Now, back in our empty `src/index.js` we can add the following: 

{% gist https://gist.github.com/LukeSmetham/49c0602e6caf3f774c45210a9c380291 %}

In future posts, we'll likely swap this out and implement `apollo-server-express` to give us extra capabilities — but this will serve us well for the purpose of this tutorial!

All we do here is import the `schema` and `dataSources` that we exported from `schema/index` and pass them to a new `ApolloServer` instance. At this point, you can run `yarn dev` and the GraphQL Playground should open up on [localhost:4000](http://localhost:4000) and you will see the feeds schema in the "Docs" tab — but we still need a couple more things for everything to *actually work* when you call one of the resolvers.

### Context and Authentication

Every resolver in GraphQL receives the following arguments: 

{% gist https://gist.github.com/LukeSmetham/72ebc84bbae127a62c1887c308aa463d %}

Context in GraphQL is most commonly used to authenticate a request and pass along the user object or ID to be utilized in your resolvers. 

One common example of this is when GraphQL APIs often have a `me` query that returns profile data for the user performing the query. This is usually achieved by verifying the `Authorization` header in the `req` and adding an ID or user object to the context. The user is then available in all of your resolvers, so you can make resolvers that are "scoped" to the authenticated user.

This leads to an important point with GraphQL Stream, and although it's a little outside the scope of this article, it's worth mentioning, even if only briefly. When using Stream in this way, you need to implement authentication yourself because the server client is, by nature, allowed to make authenticated admin requests due to the use of your API secret.

I will cover this in more detail with the Mongo and Auth examples as it will be implemented slightly differently from the context we create today. But, with the setup we have in this tutorial, there are two ways to handle this that I can quickly explain: 

1. Verify the token yourself with `jsonwebtoken` and the Stream secret environment variable as the signature. Then you can use the JWT payload along with anything else in your context to perform auth checks, fetch user data, etc.
2. If the Stream token is in the `req` headers, override the secret when creating the "Stream Context". This will make GraphQL Stream work like the client-side SDK, where users can only request their own data.

For this tutorial, we'll be keeping this pretty light and focusing on relationships between Stream and Algolia rather than authorization. However, we will be using an implementation similar to option 1 above to scope requests to the user. We just won't be performing any auth checks explicitly beyond "is there a valid Stream token in the request headers".

**So, what is the Stream Context?**

The "Stream Context" mentioned above refers to a helper method exported from the feeds schema that will add both the Feeds Client, and a GQL Subscriptions Helper for realtime feeds to the context object.

Let's create a new file `src/schema` called `context.js` and add the following code:

{% gist https://gist.github.com/LukeSmetham/b9db44684328d7cc4b7e7fd1af7ff4ca %}

To quickly explain what's going on here: 

- The context function gets called on every request, and receives an object containing either `req` or `connection` depending on whether the transport is `http(s)://` or `ws(s)://` respectively.
- We then grab the Bearer Token from the `Authorization` header (this will be a Stream User token in this case) and if it exists, verify the JWT with our secret.
- The resulting payload is then stored as `context.auth`, and therefore made available to all of our resolvers from here on out. (We could, for example, check whether this exists and throw an error if not for resolvers we want to protect.)
- Finally we call `createFeedsContext` from `@stream-io/graphql-feeds` and pass our credentials from `process.env`.

Last but not least, let's go back to `index.js`, import our context creation method, and pass it to `ApolloServer`.

{% gist https://gist.github.com/LukeSmetham/14fa7f66f125397ec60cac032595a082 %}

Just like that, we're ready to start writing resolvers, and we're discovering the power and flexibility of Stream Feeds combined with GraphQL.

**Before we continue:**

Let's do a super quick recap into what our resolver context now looks like and why:

{% gist https://gist.github.com/LukeSmetham/5b56c34adb8a6213fc9b6889f1521924 %}

As you can see above, we now have the payload from our JWT containing the ID of the authenticated user, dataSources with a `search` property with key/value referecences to our Algolia indexes (just `search.posts` in our case), and finally, the Stream context including an initialized Stream Feeds server client.

### The Ultimate Feeds Setup

Okay,  so that claim might be a little rich, but I firmly believe this is the easiest, fastest way to get set up with [Stream Feeds](https://getstream.io/activity-feeds) — especially as a beginner or feeds newbie — and start poking around. 

You can totally ignore the authentication part and only include the Stream context to have an unrestricted GraphQL playground where you can test how data moves around in your Stream Feeds application before you even think about writing any code — not to mention the frontend.

![GraphQL Playground](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/6109faf69a289fb4b140799d63bc2fc5/Screenshot-2020-12-11-at-19.37.40.png?auto=compress%2Cformat&ixlib=php-3.3.0)

> Check the [@stream-io/graphql-feeds](https://www.npmjs.com/package/@stream-io/graphql-feeds) README for quick examples for getting started with apollo-server and apollo-server-express with subscriptions and more.

I digress, but beyond the benefits of creating your own Stream Feeds sandbox to play around in, the real silver bullet lies with the ability to "teach" our datasources how to understand each other and return data in one unified object.

Back in our `src/schema/index.js`, let's start by amending the code slightly to utilize "schema stitching".

{% gist https://gist.github.com/LukeSmetham/6f38f490d86c129b68cabb7d0c0a8415 %}

As you can see, we have imported `stitchSchemas` from `@graphql-tools/stitch` and added our feeds schema as a "subschema". 

When you save and the playground refreshes, there will be no change in functionality and everything will work exactly as before. So what happened? 👀

> Schema stitching (@graphql-tools/stitch) creates a single GraphQL gateway schema from multiple underlying GraphQL services. Unlike schema merging, which simply consolidates local schema instances, stitching builds a combined proxy layer that delegates requests through to many underlying service APIs. [graphql-tools](https://www.graphql-tools.com/docs/schema-stitching/).

As defined above by [graphql-tools](https://www.graphql-tools.com/), stitching is almost definitely overkill for our use case in the scope of this tutorial. Merging can be more appropriate depending on your setup or where the schema is kept, but we can go into more detail on this in a later post.

However, implementing stitching now allows us to set ourselves up on the right foot for stitching in additional services in later articles (like Mongo, S3 etc.), and helps to illustrate another really awesome thing about GraphQL: Technically, the feeds schema (or your own extended and customized instance of the feed schema) could live on a totally different server somewhere else as a "microservice" of sorts, and you can still stitch it in and access it in the exact same way.

See the above `graphql-tools` links for more information on exactly how this works with remote schemas. But don't go anywhere just yet — it's about to get good. 😉

We now need to create two new files in our `schema` directory, one to define our custom type definitions, and one to teach GraphQL how to resolve them from our data.

First, let's create `src/schema/typeDefs.js` and add this:

{% gist https://gist.github.com/LukeSmetham/b76beb75500012f5b5100f99c075e4ea %}

In the above snippet, we are writing a GraphQL SDL (Schema Definition Language) string that tells Apollo that we want to:

- Add a type called `Post`
    - Essentially the "model" for our Stream Collections entries
- Add an input type called `CreatePostInput`
    - An input type can be used to define the shape of non-scalar argument for a Query, Mutation or Subscription (this is like defining the "model" for what the arguments of your resolvers will look like if the are not a scalar type).
- Then we extend the `StreamUser` type from `@stream-io/graphql-feeds` because our users will have names.
    - The base `StreamUser` type has two properties
        - `id` - to uniquely identify this user.
        - `token` - to optionally request the the token for this user (if auth allows it)
        - This is intentionally bare-bones so you can extend the type to suit your domain. If you want the `name` property to be an `Int` for some reason, go for it!
- Finally, we tell Apollo that there will be a `createPost` resolver of type `Mutation` that returns the newly created Activity.
    - If you're new to GQL, in simplified terms: `Query` is akin to `GET`  operations in REST, whereas `Mutation` is like a catch-all for `POST` `PUT` and `DELETE` operations.

At this point, you can go back into your `src/schema/index.js` file and amend it to import your typeDefs and pass them to the gateway schema created by `stitchSchemas`.

{% gist https://gist.github.com/LukeSmetham/bc53b85ed166990f7db9a27fd0fe060d %}

Going back to the playground, you should see our new types in the "Docs" and "Schema" tabs. Try searching "Docs" for `StreamUser` or `createPost`!

However, attempting to run `createPost` right now will fail. We have told Apollo that there will be a resolver called `createPost` but it still has no way of actually running this Mutation. We also added a new field to the `StreamUser` type — but how do we make all this *actually work*?

Back in `src/schema` let's create a new file as a sibling to `typeDefs` called `resolvers.js` and start out by creating the `createPost` resolver:

{% gist https://gist.github.com/LukeSmetham/1326d28e93520145ff5f9f68669eaa47 %}

Technically with `graphql-feeds` you can just use the provided `addActivity`  mutation, but to add custom behavior like saving to Algolia, we can use the above approach to wrap our own custom functionality around the original resolver. 

Libraries like `graphql-compose` have helpers such as `withMiddlewares` to achieve a similar effect. 

To break it down:

- We first destructure some keys from our context for later on, and check if there is a `user_id` in our `auth` payload that came from the JWT.
- Next, we take our `args.data` (which is of type `CreatePostInput`) and use the feeds client in context to create a collection entry.
- We then take the returned data, and use the `id` from the payload to reference the post entry in our activity.
- Then we create the activity by using `delegateToSchema` to call our stitched feeds schema and pass off the task of creating the activity, but instead of passing the args along as-is, we construct the feedId ourselves with String Interpolation and the `user_id` from our auth context.
- Finally we add some data to our Algolia index about the activity, and return the `Activity`  object we got back from the `delegateToSchema` call.

With this in place, we can import our `schema/resolvers.js` file into our schema in the same way we did the `typeDefs`:

{% gist https://gist.github.com/LukeSmetham/fcb65aa8f999adba9aa0798c236dbabd %}

Opening up the playground, you can now run `createPost` and your new `Post` will be added to a Stream Collection, referenced in an activity on a feed, and then added to our Algolia index for later too. 

Before that, let's quickly create a user and get a token so everything works as expected:

{% gist https://gist.github.com/LukeSmetham/1a22ad6abf53f6044f11a8ed15ed9a91 %}

This will create a new StreamFeeds user, with the name `Luke` and id `luke` (change these to whatever you like), and we are requesting that the mutation return us the token for this user if successful.

Now copy the token, and in the bottom left of the playground, open up `HTTP Headers` and add the following:

{% gist https://gist.github.com/LukeSmetham/16a6131fa9e3276fea00e5c6d11377f2 %}

And finally, the moment of truth: Create a new tab in the playground and run this mutation:

{% gist https://gist.github.com/LukeSmetham/69930dbda611b1c187df450127b2c4a0 %}

Assuming it succeeded, you just created a post, an activity, and added it to the Algolia index! 🎉

You can verify that this worked by going into Algolia and viewing the index. You should see the newly created object that represents your post.

We're nearly there! This is a fairly long post, but hopefully, you're still with us 😛

At this point, you can add activities to your heart's content — and I recommend you do! Add varying strings in the text so that the search results are varied once we implement the remaining resolvers.

 You can also run the `flatFeed` query to view your activities in a regular Stream Feed (remember to switch the `id` argument to include the user id you just created):

{% gist https://gist.github.com/LukeSmetham/b7adc0cb51b7dca5b58686d8edfcbece %}

You will see the results in the exact shape we request above, for each activity in the feed!

### Searching with Algolia

Now all that's left is to implement a resolver that can call Algolia, but before returning the response to the playground, it will revisit our typeDefs and resolve any custom fields we have in there. This is the magic of relational data in GraphQL.

For this to work, we need to create a type for the response we get from Algolia. Open up `src/schema/typeDefs` and somewhere within the type defs string add this:

{% gist https://gist.github.com/LukeSmetham/ca2195ee7cdf6685df54be3585ff2dd7 %}

If you have used Algolia before you will recognize this shape, except for one crucial difference.

We tell Apollo that `hits` will return an array of `StreamActivities` rather than the usual array of objects from Algolia that match your query. So if we were to call a resolver that returned a `SearchResponse` right now, it would fail because `hits` wouldn't match the expected response in the schema. 

This is great for utilizing Algolia more efficiently without worrying about storing data that is only there to display in the UI, i.e. the Title of an article. Algolia has a limit on the size of objects, just like Stream has a limit on Activity size. So with this solution, you only need to store an ID to create the relationship within GraphQL, and enough data to perform the queries and faceting you want. 

However, if we create a resolver for this field in our resolvers object, we can tell Apollo to do something different with specific fields. This is where the first argument of all resolvers, `source`, becomes useful.

The `source` object will always be the original, untouched response — in this case, from Algolia. So with that in mind, let's go back to our `schema/resolvers` file and add the following property to the resolvers object. (Should be in the "root" of the object, not nested beneath Mutation, or any other type.)

> Query, Mutation and Subscription are just types, known as Root types. When you create a new type, e.g. StreamActivity, it is also a root type. So to define how GraphQL should resolve its fields, you can add a StreamActivity property to the resolvers object just like we did for Mutation.

{% gist https://gist.github.com/LukeSmetham/6417ad07f8bd96d16cea1a418d683278 %}

Now Apollo knows that instead of giving you the `hits` directly from Algolia, it should flatten the results into a list of IDs, and pass these along to the `getActivites` method from Stream which returns activities in batch by their IDs.

Finally, add the following to `resolvers.js` and `typeDefs.js` respectively:

{% gist https://gist.github.com/LukeSmetham/08dce703bb5dfa4804b54c96722e1924 %}

> Adds the query resolver we can use to search activities. We only ask for the `objectID` from Algolia as it is all we need to get the activities. (Algolia has already taken care of the search.)

{% gist https://gist.github.com/LukeSmetham/216283b63ca9e43adbba04a4b86550d2 %}

With that, you can run the following query and return *actual* Stream Activities, but using Algolia as the search engine.

{% gist https://gist.github.com/LukeSmetham/9bb2c511f990c5da73ac81b7e415adf7 %}

### Bonus

From here the possibilities are endless, especially when you consider that Algolia or Stream in this instance could be any SaaS service, database, library etc that you choose. But things are still somewhat "separate" within the schema.

The `searchPosts` query will return all posts in your Stream app regardless of user, and it's a standalone resolver. However, what if we wanted to search just one feed? At this point it should be relatively straightforward to add a new argument to `searchPosts` that accepts a valid `facet` or `filter` string for Algolia. This would be great addition, but we can do better. 😏

Open your `typeDefs` again and add the following extension to the `StreamFlatFeed` type:

{% gist https://gist.github.com/LukeSmetham/b1870ef1c2c349aecf52ac1f4f8d2059 %}

Here we add a new field to the `StreamFlatFeed` type. This is exactly what we did with the `hits` field for `SearchResponse`. The difference is that there is no `search` key in the source object for a feed. There is, however, an `id` for the feed.

Back in `resolvers.js`, let's teach Apollo how to resolve the search field:

{% gist https://gist.github.com/LukeSmetham/49bb0b5009eb2c66b8d33a6780a285ad %}

This is slightly repetitive, and realistically, we could store this resolver method elsewhere and utilize the same function for `searchPosts` and this `StreamFlatFeed.search`. But in essence, we call Algolia again from our `dataSources`, this time passing a `filters` argument to the  `options` object that will scope all results of the search to the "parent" feed that we are requesting.

{% gist https://gist.github.com/LukeSmetham/caa03a2cf81773db827462ccd359fbb2 %}

And by running the above query in the playground, you can see that your `StreamFlatFeed` now has a nested property for searching its own activities! 🎉 

The best part is that if you don't request it, Algolia will never get called! The resolver will only run if it has been asked to run.

### Extra Footnotes

One topic area I didn't cover in this article is the `object` field, enrichment, and some of the quirks of enriching the `object` and `actor` fields either with Stream's own enrichment, or via a GraphQL field like we have discussed today. I strongly recommend reading this next section if you plan on playing around with GraphQL feeds — but feel free to skip it for now. There is more information on all of these concepts in the README when you're ready.

The `actor` and `object` fields in a Stream Activity are versatile. This is excellent when using Stream in any other setting, but when actually creating the schema we can't define `actor` or `object` as a definite type. Ultimately, it's up to you what those fields look like.

More often that not, these will be IDs referencing documents stored elsewhere — like in MongoDB — with which we can utilize the methods above to, in the case of `actor`, for example, add a new field `actorData`, and define a resolver for this field that takes the `[source.actor](http://source.actor)` value and uses it to resolve the full object from Mongo etc.

However, we also offer "Collections" with Stream Feeds, which can be utilized to store larger objects (like user profile data) that you can later reference in activities (like our `posts`). If you *are* using collections, it makes a lot of sense to pass the `enrich` flag when you request activities and have the data automatically appear in the feed without extra boilerplate or extra network requests.

Because of this, the `actor` and `object` fields are of type `JSON` in the schema, which allows them to be objects, arrays but also a String. This provides perfect flexibility but also means these fields are always JSON (JSON is a Scalar with no subfields) as they inherit from a shared Interface. 

So, we can't override with a new type that graphql can then resolve, and we can't define nested relationships for fields within the object or actor should they be embedded objects. (Technically we could override it via something like `graphl-compose` but depending on your setup this can cumbersome for the sake of one field. It should be easy to use this schema however you want, regardless of you GraphQL client or Node stack.)

This may change down the line as this library matures, but right now the recommended approach if you use Streams enrichment is as follows (assuming the user has an identical shape to that used in this post):

{% gist https://gist.github.com/LukeSmetham/d32c65c02bfb89f2095ffd038683668c %}

and in your resolvers you can define the resolver for the `actorData` field like so:

{% gist https://gist.github.com/LukeSmetham/ce5fd37afeff75acc0c553f83f7c40c2 %}

Assuming you called the feed like the below query (with the enrich option), you would now be able to access a typed version of the actors profile data that you can go on to extend and add more nested fields too if you feel necessary.

{% gist https://gist.github.com/LukeSmetham/61952d6d057f7e25cbb02ab77ff06518 %}

### We're Outta Here!

That's all, folks! There is a lot to go over here so thank you if you made it this far down the post! Hopefully, you learned a little about GraphQL — or at least how [Stream](https://getstream.io/) can now start to be implemented in your GraphQL APIs. Also a massive thank you to our friends at [Algolia](https://algolia.com/) for a consistently amazing product.

You can also check out the finished code for this tutorial [here](https://github.com/GetStream/feed-search-tutorial "here").

The [@stream-io/graphql-feeds](https://www.npmjs.com/package/@stream-io/graphql-feeds) library is still very young and very much a work in progress, but expect more posts very soon, where we can expand further on what's possible with Feeds and GraphQL.