Contentful, GraphQL, & Paid Content
with Stefan Judis
Learn how to combine Contentful’s powerful GraphQL API with Stripe to create paid content for your Jamstack app with Stefan Judis!
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Hello, everyone, and welcome to another episode of Learn with Jason. Today on the show, we have Stefan Judis, thank you for joining us.
Thanks for having me, Jason.
First of all, did I murder your last name? I should have asked before we came on.
I'm flexible, but you said Stefan decently, so that was nice.
And is it Judis?
You dis. But I'm open to anything. But it's also not a very common German name.
Yeah. I'm very, like, I have there are languages I've learned enough that I know that I'm wrong, but not enough to do it properly.
I feel like German
(Laughter) Fair enough. Yeah, so I am, I am super excited, oh, hey, hello, right on time, thank you so much for the raid. Welcome. What were y'all working on today?
So, Stefan, for people who are not familiar with your work, do you want to give a little bit of background on who you are?
I think the, I think there are two angles to this. So the first one is the problem of shipping multichannel. So that is a big one. And when you want to serve mobile, web, all of these kind of things, you have to have a content management suspect that is platform agnostic, and usually, all of the new ones are based on is JSON because you can use it everywhere. To scale up for the future, this is one part. And the other part for the explosion of these new headless CMSs for me is really that developers at some point didn't want to use traditional CMSs, necessarily, they wanted to do their we're doing level to level today, but they want to use the Angulares, Reacts, whatever, without having something they have to install, set up and that limits them and what they do. And I think this is where a lot of people just get excited because they can use their favorite text deck and they can move the burden to the people that enter the content somewhere else, but what people actually care about.
Yeah, I feel like this has been, you know, the movement to the Jamstack or distributed content, what I've loved about it the most is it felt like in the, in previous iterations when you had full stack monolithic things or your CMS was your view layer, you had this kind of tradeoff. There was this tension on teams where someone had to win. Either the marketing team would win, and they would get the content management system they liked and then the developers had to deal with the view layer for that content managing system. Or the developers would win and then the content managers had to deal with entering content in GitHub or doing something that was kind of unfamiliar, uncomfortable for them. And what I really like about this shift to the Jamstack world, where you decoupled the frontend and the backend and everything happens through an API connection is that now you don't have winners and losers. Content managers get to choose the tool that they like the best, the one that serves their purposes. And developers get to choose the tool they like the best, that serves their purposes. And nobody's making tradeoffs anymore. You're saying, this is the best tool for the job, and over here, this is the best tool for the job and combine them through the API connection to make sure we're building the best experiences on the web. And I think that, I feel like that just kind of kicked the doors open for a whole new wave of innovation. And I feel like we're seeing that, again, with corporate sites that are getting more and more creative, but also this explosion of personal sites that are getting super creative and interesting. I'm really happy to see that happening, again.
Oh, I totally agree, and I work for several companies where editors and content editors had to really this scenario with editing, this is a matter of time until everybody gets frustrated. And when developers set this up, you're not everybody's friend there.
That is absolutely true. And I've been in those meetings where it's just, like, you can tell that everybody's unhappy. You know that, you feel that there's probably a better way to solve this, but you're making a compromise and, you know, the idea of compromise is great, but when you're talking about it in a tech stack, making a compromise, like I will be unhappy with the way I edit content, that's not a good compromise. That just means you're adding friction, you're adding complexity, you're making it into something that is going to prevent people from wanting to make content. And that means, you're going to be slow to adapt, slow to make changes, slow to generate new content. And that's all, you know, that's bad news for the business, bad news for everybody. That's going to drive morale down.
I think speed is also a big factor here. So with the new CMSs out there, you can separate these two things. You can take a step back, define your content structures and then teams can work in parallel, which you couldn't do in a monolithic thing. Everybody was working on the change. Everything's tightly coupled. With the view layer, and this is a big advantage when you want to move through the decoupled approach and works fairly well.
Yeah, I've seen it across multiple teams now. And the unifying truth of all of it is that every team I've seen move to this method, they've been faster. You know, it comes with its own set of confusions and frustrations because now you have the opposite problem, instead of having one tool, you have dozens, and trying to figure out which tool for which part of the process, that can be a pain. But
Yeah, if it's not one thing, it's the other. With that being said, let's talk a little bit about what we're going to do today. And I'm going to flip over to the pairing view because we're going to be looking at a few tools. In contrast to a standard episode of Learn with Jason, because what we're doing is relatively complex, we're going to start from a previous episode of Learn with Jason Thor, who is in the chat today, hello, Thor. We worked on a subscription tool where we were able to add the ability to gate content. You see you need a subscription to get any of the content. Once you log in, if I can remember what my demo password was. I think I did it. Here we go. I'm logged in, and I have a free account, so I can see the free content. And then, if I want to upgrade, so I can manage my subscription, and we're in the demo mode here, so I can change it to whatever. So I'm going to go to the premium plan. I want all of the content. Confirm. And this upgrades my account, and now, that I'm upgraded, I immediately can see all of the premium content. So the way that this works is heavily detailed in this article here, if you want to see how that works, you can just grab this one. And the source code is all here. And in case you want to watch us build it live, this is the episode that Thor and I did where we built this episode, or where we built that code. We're going to be starting with this code as our starting point. And we'll be expanding it to include Contentful. So we'll walk through this code a little bit, but if you want the deep dive, read the tutorial. What we're going to be adding today is Contentful. So if you want to talk about, if there's somewhere on the website or specifics you want to talk about with Contentful, we can do that here.
So I think I can just give you two sentences, and then we log in and set things up.
So Contentful's strengths is that you define the data structures on the fly. And then, you can vary it with the API. And what I would like to do today is to use GraphQL for that. That just became free at Contentful. Let's using the cutting edge stuff here.
And I think the rest will explain itself when we do this.
What I would ask you to do is log in.
All right. Now, we get to find out.
OK. We've pulled this off screen, over here. Oh, come on. This one, there we go. And somewhere I have Contentful. Good. All right. So I think we need to create a new space, right?
Yeah. So space in Contentful is a new bucket of data, limited data. I think free plan, this space that you have will do, too.
OK. Yeah. What I did was I set up the knowledge base.
So there's a cool thing that Contentful just launched where you can click a button and it'll automatically deploy a knowledge base to Nulafy to all of the configuration here. So yeah, we have some content, right?
Yeah, we have some content. Maybe we want to start with fresh content type to walk the people through where this is coming from.
Yeah, let's do it.
Go to the content model section in the top, reading the top left bar. Yep. And here you could create in the top left corner.
Oh, I had my window too big. Why is everything cut off? All right. I'm going to add a content type.
So want to go with a post just to map what are the content types on the site that we have?
Well, the content types here are actually just, they're just, they don't have any type really. They're unstructured. So it's just kind of
I mean, I mean from a what they are, they have an image, they have a text, probably we could call them posts. I'm just looking for how to name this bunch of data.
Sure, sure. We could call it, yeah, call it, he's suggesting product.
We go with product.
Why not? Let's do it. Let's call it a product.
Cool. And this is all it takes to set up a new content structure. You see a name and this will have an API identifier.
And create this, we created a new kind of type. So when you think of coming from a restful world at the API point, and you have people slash something and posts slash something. We can define the structure of this thing. Every content type, then it has fields, exactly. And so, we can now do, is we can add a text, which is probably something like a title.
Yeah. Let's maybe pull this one over to this side.
And we can look at this content.
That looks good.
So do you want to get into, like, media types? Or do you want to bring over I just did these all as text fields. So how would you like to tackle this here?
We could also upload these images very quickly, that shouldn't take long.
OK, cool. Yeah, let's do we'll go image alt.
Would go into the image itself.
Now, tackle the string field first.
Would the, do the credits and credit links go into the image field, as well?
Contentful has two field, a title and description. We could probably put it in the description to unify these.
No, that's OK. Like, I think we don't need to customize or anything, I was just making sure that there wasn't a built in image attribution model or something.
The thing with Contentful, it's really very flexible to all of the things. If you need something, you can mash together certain content types.
For now, I would really, with this one, just start with the title or something to give this entry, which is a product a name, first of all.
And then, we could add another field, which is, for example, then a media asset, which you see below.
And what do we call it? Image. Image works, usually, I would say.
Yeah, I mean, especially in this case. And this is going to be one file. That seems like the right thing. Yep. So we can create this one.
And then we see that we have a message in the predefined data.
We have two kinds of field types, usually something like a markdown or something, but in this case, we can go with a short text field. That works fine. And then, we would have to come up this allowed roles kind of thing, which we're going to use to figure out what is actually accessible.
And what we could do here is we could also use a string field, we could use it as a collection. So when you add now another content type, for instance, another field.
You could use text, again.
Yep. And now, you can take the little list box on the right hand side there. Yep. And now, you could click, create and configure.
So basically, we're now setting up is an array of lists.
You can go into the validation tab. And the last one is accept only specific values.
It could now go in with free, pro and premium. You can hit enter here.
Yep. Nice. Cool.
And then, the last thing we can do, we can change the appearance. And also, the top bar of the type. Yep. And I prefer the check boxes, what do you think?
I think that makes sense, especially for this one.
Yeah. I think so too. And we can save that.
And when you go into the interface, so these are first fields. And I think it makes sense we start querying data and click always extended.
OK. Let's make this bigger, again, and go back in here.
And then, I save.
Save, yep. And what we now just did, we set up the content structure that we are ready to set up for, like, we talked about speed, right?
We can set this up, and now, we could give all the people, other people could go to the content tab and really top left bar, and really could now start creating. If you click now, add entry in the top right hand corner. We should have our product there.
Start filling this with data.
All right. So let's do free corgi and create a new
Let's grab, let's see, this is our free one. So let's go to this here. And I will just, I can just download this real quick.
Oh, maybe I can download this really quick. Here we go. And then
You can upload it, and right.
So here's our corgi.
Nice. That's a bit corgi, huh?
Yeah. It's pretty large.
We give it a title and description. It pulled in free corgi for us.
We could use a description for the outtext, for example.
The alt text? OK.
Here's our alt text.
Now, we can hit publish here. The idea of draft and publish content. So whenever something's published. It's publicly, you can fetch it via one API end point for the published stuff. And we have unpublished data and draft mode. For that, we have a different API. So when you want to build, for example, preview things or something so others can see stuff before they hit the big green button, you can fetch that data in a different way.
Got it. OK.
With this, we now set up our first asset, and we can close this, again. By clicking the, yep. What also we're missing, so message, which we had in your data, which is
I mean, yeah, what we were doing with this, we were trying to metacommunicate with the content so it was clear what you were getting. And this one is free only. Cool.
We could publish this and basically, we set up our first content entry.
OK. So what we are missing on this and I guess we can go back and do this later, we're missing alt text, or the credit and I want to make sure we don't forget to credit the photographer. Do I just go into the content model.
Wrap our image into another content type. Holds an image with title and description which would halt information to just kind of assemble data in different pieces.
I see what you mean now.
I think it makes sense to add another field and say credit.
Yeah, that makes sense. OK. Let's do that. We'll do credit, that's going to be short text. And then, we want another one for credit link, which I assume is text?
This would also be short text. Credit URL. Credit link.
Is there a special
If you want to, you can configure it so when you go into the appearance field.
Oh, appearance, yeah.
You could use URL and you would see a preview in the editor interface, if you wanted to.
Yeah, why not. Let's take a look at that. That sounds cool to me. Let's go back into the content. Here's our corgi and our credit. So let's get our hey, thank you so much for the bits. Very much appreciated. So here's our link. So I'll drop this link. And then, I apparently can't select this guy's name without going into it. So let's go here. All right. OK. So now we have, now we have, like, everything we need for that entry. Do you want me to do the same thing for the other two?
We can do it now, or we can start clearing the data first, whatever you prefer.
I'm following your lead. You're in charge.
Then, let's set up the other ones, too, really quickly.
OK. Cool. Let's, let's
And then, we've got, we had to make up our title. So this is pro corgi. Great new asset in the link. Saying you can do this with the URL to upload directly, is that true?
Can you click the drag and drop interface and open the file selector?
Pro tip here!
All right. So let's grab this. Here's our URL.
Beautiful. Oh, look, it even pulled it in at the right size for us. That's nice. So we should probably go back and do that for the other one, too, so they're all the same size.
I think this comes from the on splash URL, though.
Right. Right. So well, actually, here's a question, I know that Contentful has its own, like, image media manipulation, do you want me to do these full size so we can do that?
We can do it with Contentful, too. If we want to clean it up and use Contentful, it's probably better to, so when you do it with content, you probably want to have the high res stuff in there.
Let's do that instead.
Let me trash it. Get out of here. We're going to get a bigger corgi. There we go.
Yeah. Big old corgi. And then
Would it be alt first?
OK, here's our alt. Goes there.
This is our pro corgi.
Well, that got easier not having to download those. And I can come back. I need to publish this?
Yeah. You need to publish it.
OK. It says published. Oh, this is the trap, that's why.
I'm not OK, let's see what happens.
We're all learning together. So here's our message, this one is available on free and pro. And our credit is Laurencia Potter and here's the link.
OK. There's our pro. And we'll do one more. Let's see, did it do the full thing? That one's still marked as a draft.
Can you just change something? Unpublish and publish it, again.
Yeah, we'll unpublish it and then we'll publish it.
OK, we're all good. We're published.
And one last one, then.
Yes. Premium corgi. All right. We'll create a new asset and a link. That's going to be this one here and we'll skip the
OK. So this is premium. Open, do the URL here.
Yeah, that really is nice.
I feel like that's kind of a game changer. That's the sort of thing that makes a big difference. If we'd had to go and download each of those, you know, I feel like we'd still be half way through the second one right now. OK. So that's good.
Publish that one.
Publish. All right.
Come back out here. And then, we need the message.
It's available only on
Only on premium, huh?
Oh, I did that wrong. We need to make sure it's only available on the highest one. And then, we've got our and URL.
Cool. Good deal.
So with this now, we have three corgis available, and I think we could start querying this data with GraphQL.
Yes, I'm going to fix this so it's only available on pro. Now, I'm ready.
Yep. Cool. Now I would just Google Contentful GraphQL to quickly spin up the documentation to find a GraphQL, which is a tool to
Can you just, no just Contentful GraphQL or to the doc there is
That looks good, and now do command F and look for GraphQL. GraphQL comes with its own tooling. Yep. And which means that for GraphQL usually, you don't have to read a lot of documentation because the tooling provides you all the things that you need.
What you can do now, grab the URL. We will grab them in a second. The first thing, you have to say the space you want to query the data from. You get that in Contentful.
Is that this one?
Yep, URL does it.
That looks great.
And we need a CDA token, this is where this episode comes into play. So the thing is with Contentful is that all of the published content that you have in there is accessible when you have this token.
This is why we're going to do our serverless function to hype this token to the public because otherwise, people could just, developers could figure out oh, this is running Contentful, and here's the token. And could just query away all of the gated content we want to deal with. This is where we're going to hide it. And when you're not dealing with paid content or gated content, that's usually ideal. The rule with headless CMSs you should only have public content in there anyways unless you have a special place like we do right now.
We can now go to settings and the top right. And we can go to the API keys.
And you can grab the example key or create a new one.
An example key. Yeah, maybe I'll take this example key. Or, wait, do I need content delivery? That's the right thing. Example key.
Yeah. And the space ID and the content API access token.
Is the one that you want to grab.
And these are public or do I need to roll these over?
So the content delivery API one is assuming that you're using Contentful for public data and, for example, you're building a different application, you have that on public anyways at some point. Because, for example, when you do, I don't know, React or Angular, you have to have this in your request to be able toing a success the data. So with the delivery API token there is not much risk. For the preview API token for our little case, it is the unpublished data, which will become public eventually.
So you could roll this, but I would say for our sake, it's not a big deal.
This one, then, only needs to be private if you're worried about someone snooping on draft content.
For example, you have your product releases and want to have that token somewhere.
So there's a question in the chat about Brandon's asking, like, so would you not use a headless API if you had any private data?
The idea of CMS public data. If you want to build your infrastructure to hide all the tokens, you could do this. But the general case for these systems is to publish two platforms. And if you want to go for other use cases, you have to be really careful that you don't leak the tokens. And
So I will say, like, kind of a yes and to that, what we're doing today is we're going to have private data in a headless API. And the way that we're going to do that is we're going to use a serverless function as a proxy. When you use a serverless function, all of the tokens you use are kept private. They're not accessible to the outside world. So, for example, if you wanted to load your Contentful content on your site, you could just query all of the content in a serverless function, and that would keep that key private so someone wouldn't be able to use it and hit your API for whatever.
Yeah. You have to be very careful with the tokens when you want to do things like we do right now.
OK. I want to get product collection. This is one of the things I like about GraphQL is that I just clicked this button here for docs and I can just see we start out with the schema, I can query, it's read only, we can only read data and I can scroll through and see what my options are, and this is what we created today. Product and product collection. And I want my collection back, not a single product, so I can just do something like this. And I'm going to just get them all. Let's get our items.
No, looks good.
Oh, that's got some fields.
Beautiful. Here's our description for our alt. What else do we need? A credit, credit link.
Allowed roles. And did we have one more? It was a message. I think that was everything.
That is gorgeous is what that is. And this is really where I think this gets so exciting. We now have a fully functional data API. And I can hand this off, like, I could send this to my content management team, and they could create new protected content without ever having to look at the code. They only have to look here. This is the only tab they ever need to look at. And they can control everything on the website. And I only ever have to deal with this API. If I'm on this API, I can get all of my items and I'm super happy because I have exactly what I need and nothing more. It's really, really nice. So, OK, I'm
That was a quick rundown on GraphQL here.
I'm a big fan of GraphQL. And oh, actually, let me do a quick shout out. If you are interested in learning more about GraphQL, after the show today, the GraphQL Summit is live right now. You can jump over there and learn more about what they're doing. I'll drop that link in the chat for you. That look at this, here we go. So we've got products. We have products, we have the ability to, you know, right now our content is loaded here.
So I think if I'm understanding correctly, all we really need to do at this point is swap out where we're getting our content from.
Correct. Now, we could go into the serverless function that currently gives us the content and we could fetch the data using the GraphQL API that we just already wrote the query for.
Perfect. Let me fork this, and I'm going to put it on Learn with Jason. And let's rename this to, we'll call this, what do we want to call it? Contentful, Stripe Paywall?
OK. So we have our pay wall now. And in here, I'm going to get this cloned. I'm using the hub tool to do this with shorthand.
Yep, getting the code down.
Am I using YRN or NPM. NPM. Let's get our basic set up here, and I believe, I can just immediately deploy this. So I'm going to get this set up on Nullafy to deploy, which I will do from the command line, which is nice. Nullify init. I can hit configure. We're going to call this Contentful Stripe Pay wall. So that's basically deployed. So if I open this, this is going to fail because we're missing environment variables.
Yeah, tokens for stripe and Fauna and we would have to need to add the Contentful token.
Perfect, my variable thing is handled here. What I'm going to do is I'm going to use the same set up from the other one, so we don't have to reconfigure Stripe, and I want to make sure we don't run out of time. Here's Stripe subscriptions. Let's go into our settings, get those environment variables, and then, I can just grab my Fauna, oh, I've got to edit. My Fauna server key. Did I not open to, hold on, let me open another tab to just move between. Build in deploy environment. I have a Fauna server key. And then, I have a Stripe default price plan.
And this is the name of an in Stripe you create prices, basically. Then, we've got a secret key, and that secret key is how Stripe identifies that we actually own our account. And then, we have a web hook secret, which is how we can make sure that somebody's not spoofing requests to our API. And the next thing we're going to need. What's this called?
CDA, content delivery API. And we have it still in the URL here. Yep.
Should we include the space in here, as well?
And it's a good call. Contentful space ID's needed.
Let's get space ID. We're going to drop that in.
We have everything we need here, and here's what's really exciting about this. Now that we've added these, if I run dev, now, we can work instead of setting up a .n file or anything like that. We've got our content and did not work. My site is configured for the wrong Nellify identity. I'm going to clear my local host URL, go out to this one we created and I'm going to enable identity.
That makes sense.
OK. Now, that I've enabled identity, I am going to take my URL here. And I'm going to put that into local host here. And so now, what we're doing is connecting our local environment to the published site so they're all part of the same thing. And nobody is signed up for this one yet. So we'll sign up really quick.
The tokens and the secrets is a question in the chat.
That is a plugin from sorry a Chrome extension from Sarah Drasner called Nellify. Well, she specifically built this one because the Nellify environment variables are not hidden. Like, what we should do, and this is something that's on our backlog is we should have a visibility toggle for our environment variables. But right now, we haven't done that. So Sarah built this in the interim that makes it faster to it was, like, this is a band aid solution. Eventually, we'll solve this for real. OK. I have a confirmation message. I'm going to go to my email really quick. And then, I'm going to copy this link and what we get on the link is a big old long hash, come on. Never mind, it's an Mandrill app. Don't give me that. I want that hash. Why? Also, totally validated my account. OK. That did work, means over here now, I should be able to sign in. If I go to log in. My email is confirmed. What? I have to get this you're not going to show me that hash, are you? OK. Why doesn't it want to take do I have something hard coded here? 402, payment required. That's what we would expect. And then, if I, token. I'm not signed up. OK. Let's look at the code really quick and see if there's something that is happening that would require this to not boot. Let's open this up. And we have our functions and we have our source code. So in our source code, we can run through this. We won't run through this, the CSS makes it look nice. Log in button and our sign up button and then, we have our actual content. The content is by default just an empty div and we load the content you have privilege to see into the divs asynchronously. This is the content, loads a figure and the credit and everything. We load in Nellify identity, and then, down here we have our buttons, our buttons run the log in and sign up actions and those happen whenever you click one of these buttons. And then, when we update the user info, we change these buttons to log out or manage. OK. That makes sense. Now, the part I'm trying to figure out is why, what is this thing doing that makes it think I can't validate this user?
Is there some other configuration we missed?
I don't think so. I almost got it. There was an error validating your account. Let me try to sign up through here.
That should have sent me another confirmation email. I'm going to automatically confirm.
You can just resend the email? Or you got the email the second time?
Yeah, I thought I did, here's a reset password email. See what happens. Database error uploading user. I'm going to delete this user and try, again. Here's this user, delete. Delete. Good bye. All right. Now, I'm going to sign up. That says it should have worked. Let's see if we have a user now. Says it created the user. Here is a confirmation link. I might have just done something weird where I signed up, I was on the old what? Why? Why are you like this? OK. So the, oh, maybe it's the identity sign up? But that should work.
Does it still work on the base that we cloned? You have that still on Nellify, right? The one that you built with Thor?
Yeah. But this should all work too, and we did deploy this. It was not deployed because it was broken, which means I need to oh, oh!
I'm not following, what was broken?
It was missing all of the environment variables.
It'll take a quick second to explain what went wrong. So when we cloned this repo, and set it up on Nellify, it automatically started building the site. And the site will build fine without environment variables, but it will be missing all of the it'll be missing all of the functions. We can't do the extra stuff. So because I didn't rebuild the site after we set up our environment variables, we had, like, a partially functioning site. Which means, now, if I, theoretically go and do this, again, because we just rebuilt. Do the thing, does this work? Yes, so that was the problem.
What you're basically saying, the function that is connected to identity was erroring out because the values were empty, correct?
Yeah. So what was happening, when we have the way this function works, or maybe we should walk through the code here. When you sign up, you're going to get sent a confirmation link. And then, whenever you create an account in Nellify, you have an option to create this identity.sign up.js, and what this will do is get the new user that was created and let you do something with the user. So what we're doing is grabbing that new user and creating a new Stripe customer. For that Stripe customer, we're creating a free subscription. And then, once we have that free subscription, we save the Nellify ID and Stripe customer ID into Fauna. And so that Fauna content is our map of which Nellify user is our Stripe customer. Once we've saved that, then, we set their role to be free. So we immediately create an account and give somebody the free subscription when they sign up for the app. And that's how all of that works. So what we were doing, we were missing the Stripe secret key, this default price plan, and so when it was trying to load, this function would just throw an error, which was causing the whole sign up process to fail. And that was, yeah. So that was a confusing error.
That makes a lot of sense.
I bet if I look at the function logs, that would have been far more clear than just guessing at what was going on..
That's something like missing token or something or missing
I think we can see that if we look here. We should be able to look at the functions. And we can see our identity sign up. Oh, no, when we redeployed, it cleared the logs. We would've seen the errors here in this new function log.
So now, we have
Back in business.
We're ready to roll. What I can do here, this is what we're doing here. We have our content. And the way this function works right now is that we get in the type of content. Free, pro or premium. And then, we grab the currently logged in user out of context, client context. And if we look at the actual call for this content, it happens, get protected content. We are grabbing the user token out of Nellify identity which is a JSON web token. And then, we pass that in as our authorization header. Because we're using Nellify identity, we don't have to manually identify, Nellify's going to do that for us and that gives us this user. The client context that has our user in it. So we can just pull out the roles. And if we look in Nellify at the identity, the role of free was automatically applied when I validated my account. As far as the code is concerned, the first time we call this, I'm trying to load free, premium, pro content, my user roles are free. And then, I'm going to check here if the roles match any of these. So basically, if the allowed roles include the given role, I was wrong, we do have to, we do have to check the box for all types in Contentful. Because this is going to say, like, if I'm a free, if I have a free role and I'm trying to run premium content, I should be able to sorry, if I have the premium role and trying to load the free content, I should be able to see that. We want that to work. If it doesn't work, we bounce him out with a 402, which is the http code for payment required and we send a placeholder, which basically says you need a subscription. If not, we send the content. And that is the whole magic of what's going on here. We're checking to see if the current user has the required role to view content. If so, we send them the content. So what we need to change here is where we're getting this content from.
So, I've already seen you have function to fetch from Fauna. I would have a look at this one and probably create a, yeah, exactly the stuff we need for Contentful. So what we can do is create a new function called Contentful.
We're going to use a whole bunch of the same basics.
Yep, totally. Totally.
So we're going to use node fetch so we can use the fetch API, which is a browser default.
To make http requests. And now, we can, we can, basically, we should be able to only change the URL, which is the first parameter. Yep, let's grab this one.
And we're going to swap out.
I would now do here the explore. We can remove the explore there.
Remove the explorer.
Yep. And I would usually pass the token because this feels more like GraphQL natural to me. So we can get rid of that one, too. And the space ID, we don't want to keep this hard coded. So what you see is Contentful spaces, we have this Contentful space id. Yep. And then, we would have to set the authorization bearer token.
And the bearer would be our space id, right?
No, the CDA token. And then, we would have to define our query, which we will get from the top. That looks good.
We will use. We should rename it from Fauna fetch to Contentful fetch.
That seems like a good idea. That will get confusing in a hurry otherwise.
And I know that the Contentful API meets the content type to application being set instead of the header subject. We can define custom headers from this, I think you have to quote it because we're using a dash.
Yep, you're right.
And this should be application JSON, application JSON. Is that right?
Yeah, application JSON is correct, I think. And with this, we can go into our get protected content. Contentful fetch.
Out of our utilities.
And then, load
Content, I would below, so line 33, I would also define Contentful query first so that we don't have it in all of this logic, too. So the query that we have in the GraphQL tool, and what we're going to pass.
Yes, so let's grab that here.
And then, we're going to have to figure out. We need to load the content.
So what we're going to do is wait Contentful fetch.
Yep. Content await, Contentful fetch, and it'll have query.
Yep, and you can pass this. Yeah.
OK. And then, I think for variables we didn't declare a default. I'm going to put it in. An empty object. And then, once we get that back, I think we can take the response and response.JSON.
Wasn't it in the function, actually? Can you go to Contentful?
Did I do it already? Oh, I did. Good call. All right.
Await should do the trick.
Await should do the trick. And now, what's going to come back is going to be different from our data type before. What was happening before is we were getting, I'm going to rename this so it doesn't give us big errors.
We had the role, and then, we had the data. So we have a couple of things we've got to figure out. First, we were getting the allowed roles by using the content and then, getting the type that was requested by here. For this, we're going to have to change that by doing, let's see.
You could iterate over the object.
Yeah, we'll have to do a actually, let's do this. Let's rename these to the role types. That will be faster than trying to write specialty stuff. So let's call this one pro. And then, it's going to need to also include our free plans because the free, if you have a free role, you'll be able to see. And then, premium is going to be renamed to premium. And we want to check all of these boxes. It's going to be free.
So then, when we do this query, we're going to have a title of free. And so, what we can do is filter down instead of, instead of doing the content type.
You can filter the title?
Yeah, so we can do a content. Let's see.
You want the content. We'll call this requested content. We'll be content.find and we can do, like, c.title equals type. And that gives us the actual content type that was requested.
You have to go down, though. So content currently is something with product collection items, so when you look at the
You're right. You're right. Actually, let's do this.
Yeah. That is better. That is good.
So that will be the response..
The data, I think.
Does it come back as data? I think I do that already. No, I don't. What does Fauna do when we do this? Does it give us Fauna fetch result.data, you're right.
I think this is GraphQL. And GraphQL response, you have all this data because errors can come, too. And then, you have data, and if there's errors, you have to check for this property if it's available.
Got it. Got it. Yeah. So we'll just kind of assume that it is going to work. And then, for the allowed roles, then we have those here. So once we have our content, then we can just say, the allowed roles are coming out of the requested content. OK.
That looks good to me.
And then, we would just return the requested content.
What we didn't do is clean these up. What do you think? Should we map these to map this object shape? Or
I would honestly move the template quickly.
Let's do that instead.
So we will go out here and we're going to get back our let's see, we get our data and our data is going to come back with a let's make this over here. And we'll put this over here and make it as big as we can. So in here, we're going to get back data. Our data is going to be data .ism imagine.url.
And image description.
Data.credit. Those stay the same. Data.message.
I think we're OK.
Yeah. Let's give it a shot.
Let's give that a try.
Is our site running right now?
Cool thing, we can run where the function set, right?
Yes. You've got Nellify going here. Running, good. So let's log in. OK. Things are going wrong. But not all is lost. So let's figure out.
We got a response.
Yeah. Let's figure out what that response was. Let me drop this over to this side so it's a little bit bigger, and we ran get protected content and it returned title, message, image.
Did we miss something here?
Did we get this wrong? I think you can reload the page, maybe? I needed to reload the page. OK.
I'm noting one thing is missing here, which is right now, we are loading if I pop this open into a new tab, we're loading 6 megabyte monster. Can we clean this up a little bit? Oh, we've got so much time left. I thought we were right on the wire. OK. This is great. We've got some room to grow here.
Here, to do that, images, you can do that two ways with Contentful. First thing, every URL for an image on Contentful, you can rework the parameters. We can do, too, we can use the GraphQL interface and figure out what is regular. And GraphQL will figure out the URL. Does that sound good?
GraphQL will do it for us?
It will build the URL for us. Yep, and we can go product collection, go into product collection, and product image image is an asset. Yep. And here, we have
Wow, this is cool.
We can drill in and find what we want to do.
So URL, transform.
I want to get a width of, what was the width before? Let me check. I have too many things.
It's a little bit handier than defining the URLs.
That's a game changer. Let's see, we were doing 600 by 600.
I think we have the same parameters available, too.
So let's go, let's see, where did I many GraphQL go? Here. So we want 600, and we'll go height 600.
And then quality.
Wow. Look at all those. That's cool. This is really cool. What does that mean? Let me click into this. I want to see what these are. Defaults to fit. Oh, wow. OK. That is really cool.
If you're resizing the image, you can crop it and define, OK, where should it actually go?
Excellent. So we want to use that. We're going to do a resize of fill because we want it to fill, not, I guess, they would all
Because we're defining dimensions. When you have an image on the landscape and you want to make it square, you have to figure out what you want to do. Do you have padding on top of the button. Fit it in there.
I understand what the difference is. So fill will expand it.
The longer one.
Is that right? If we try to, if we tried to crop the dimensions that we wanted were bigger than the image, crop wouldn't do that. But fill will. Fill will, like, blow up an image. If I understand correctly. I think that's the major difference.
I think we can just figure that out, right? We're dealing with a square here, we have the original and we can see what happens.
We do. So let's go.
The URL now has the parameters that you need.
So let's try, let's make it to 6,000, and I've got it on crop right now. Valid transformation.
Maybe I'm outside of the what Contentful considers to be acceptable. Let's try 3,000.
Looks like it.
It's working on this one.
So with that one, did what we wanted. With this one.
With this one, it makes, makes it wait.
I think it.
So I think fill will force it to fill the space whereas crop won't. I don't think this image is 3,000 tall. So it wouldn't force it to be 3,000 tall to avoid, like, pixelation where fill
You fit into the dimensions.
And if I blow it up to full size, we can see where that's happening. This is how big it is, we can kind of do this to show here's the three boca points. If I set this to crop and blow it up, our three boca points are these three here. And it had to expand the image in the fill strategy. So that's, that's just, it telling us, you can't, you can't break our stuff. So I guess in our case, we probably do want fill because if someone uploads an image that's too small, we wouldn't want to break our layout. So I can take this. I'm going to move this directly over into our code. And it goes on URL and save. And we should see immediately. Boom. Look at them go. And now, if I open these in a new tab, there are 600 pixel wide corgis. Excellent.
Can you open GraphQL, again? Let's check if we get the image dimensions. Right now uh oh. What happened?
My logic wrong. Why is my logic wrong? So right now, I have the free plan. Which means, I have the free role. Which means, when we are doing our allowed roles.
Someone said we ticked the wrong boxes.
Your logic isn't wrong, Stripe just doesn't understand you. (Laughter) That is yes. That's exactly what it is. While you're talking to Stripe, can you also tell me mom? So I have my roles. My roles, how do I do this here? Free? Free gets all of them.
Oh, I did it wrong. You're right, I did it completely backwards, thank you, chat, for teaching me how to do this stuff. I swear, one of these days, I'm going to get this stuff right. So free, pro and premium. Everybody can see the free content. So you check all three boxes for the free content. And then, only premium and pro accounts
This is premium. Only premium can see premium. And only pro and premium can see pro. So that should fix it. I think if I just
The logic here.
It bends my head. OK, so go back.
So did what we wanted. Gave us a 402, but it didn't send back. We didn't change the shape of the content. For this one now, it needs to be image URL. And description.
Wait, but this is a dummy content, isn't it?
Yeah, but we changed the shape of the data. What we're reading for. What am I missing here? Property of URL undefined. Enter index.HTML, we're looking for
We're looking for data.image.url. Blocked the content. I don't think I blocked the content. It should be returning if the roles
This is not correct here, right? This should be different, shouldn't it?
Isn't that what I changed? Where did I just change that data?
No, we changed the dummy data. Yeah.
We're getting somewhere, Jason, we're getting there.
OK, all right. Let's fix this for real, then. So here's our description. And here is the URL. So that should work. Let's try this one.
That looks good.
There we go.
There we go.
If you write the code properly, then it does what you want it to do. All right. Good. We've got it. We are effectively, now, protecting content in the Contentful space. So if we go back in and we want to swap this out. Like, let's change with our free content is. I can go find on splash, let's find more corgis. There we go. Let's get this image address. And we'll go in here and we'll say, I want this corgi to be a different corgi. We're going to trash it. I feel bad saying that. I didn't mean it, corgi. You don't go in the trash.
Get rid of that. And then, we will get about to flow. Here's the new credit. The meaningful alt text, we'll publish, update the credit. Once we've published . We've done it. We now have a content management system hooked up to access control. So if I go in, and update my subscription, we want all of the corgis. Actually, we did pro, I'm going to update to the pro plan when I'm in test mode. It's alt 402, and any valid future date and any ZIP Code will pass. If we continue, I have a pro account and let's go back and look at it.
I'll take the feedback, it would make it more clear that the URL works.
Make it clear that which URL works?
That the URL and the image upload works, really.
I did something wrong. Which thing missed?
You should have URL in Nellify.
We didn't tell Stripe to send the web hook. So it didn't update our
Well, it probably updated the wrong site, didn't it?
Yeah, sent an update for a user that doesn't exist. So let's go into Stripe. What was that? When did I enable that is a better question. OK. So we did this with corgi subscriptions. And so, we're in test data. If I go to my developers and web hooks, I need to set up a new web hook. So right now, we have one web hook, which is the live, like Jamstack subscriptions. We want to add a new one for this site. Where is it? Here. So I'm going to take this URL, and let's take a look at what the web hook is. So we start here, and then, we need to get into the functions. And the function that we want is handle subscription. So our handle subscription change, I should say. So let me set this up and then, we'll talk about it while we're waiting for it to Nellify, functions, handle, subscription change.js. And we want to send which one? Customer subscription updated. That one. I'm going to add an endpoint, OK. And now, I need to get this signing secret. And this is in test mode, I'm going to reveal it. What? What? Oh, yeah, without the js part. Good catch. Thanks, chat. So now that I have this, I'm going to head into my overview, go into the environment variables, and I'm going to swap out my web hook secret. Now, it's going to send to this site and we've got a different web hook secret for that. So what's happening is whenever we make a subscription change, whenever I fill up that form, it's going to send an object to this endpoint. And what we'll do with that is we grab that data out. We use the Stripe library to make sure it's a valid request, so that somebody couldn't just say, like, you know, oh yeah, good call, let me start the rebuild.
Yep. Kick off the rebuild.
Fortunately, this is a very fast site, so it should build pretty quickly. So while we get the web hook from Stripe and we validate it's legit, which we do by sending this web hook secret. And that prevents from figuring out what the web hook for subscription changes. And saying, hey, I'm premium and never setting up their account. And then, we check to see if they, if it's the right event. If it's a customer subscription updated event, if it's not, we bail immediately. If it is, we get the subscription object, we go and find the Nellify account ID of the person with whatever Stripe ID updated the account, get that from subscription.customer, pick up the Nellify from the results and turn the subscription plan into a new role and send a request to the Nellify Admin URL or API with the new role. So we update the app metadata roles. And so, this identity, this is the, like, users can modify their user metadata, so you probably don't want to use that for authentication. App metadata can only be modified by the app. And since you're in a serverless function, you can modify your app. So we get the identity Admin API out of client context and it gives us an API token that's very short lived that will let us make requests when we have this function running. So basically, what we're doing is saying, hey, I'm an Admin, I'm the app, I want to change the roles for this user. And then, we update them to whatever the level of subscription they had. And then, we just send back some kind of acknowledgment to Stripe so it doesn't tell me that the thing is broken. So now, that is done, that's deployed, everything worked. Let's change our subscription, again.
So I'm going to manage my subscription. And I'm going to go to free and then back to pro. And we should be able to see in the dashboard here that when we reload, there will be web hook attempts. Have these just not sent yet, or hmm. Am I missing a piece? Let's see.
Did the call come in?
Go to the events tab. Events tab. Yeah, it's not doing the thing. I'm in test data. I'm giving a bunch of people's emails away. That was bad. I shouldn't have done that. Web hooks. There should have been an event there.
You hackers, you dirty hackers.
So, what's missing? Why isn't this happening?
Can we open the function logs to see if a call comes in? Just to be very sure?
We'll go to handle subscription change. Not showing me any requests.
Total again something.
Yeah, so let's go here. Did I just typo this? Did I just get it wrong? So my web hook.
Contentful Stripe paywall.
Doesn't need a slash or something at the end, does it?
No, that's the thing. Customer subscription updated. I probably typoed this. So let's just try it, again. Good. Good, good. I'm going to send a test.
So this is the data that we give back. And it's going to tell us, yeah, it's got like an ID and stuff.. And now, we got 400 because there's no Nellify identity user that matches. But it sends in our data, our object, we get our subscription back. All right. So that should all work. And we should see here a call. And we, got a call, didn't fail, it did what it was supposed to do, which was respond with a 400. That means, it is at least hooked up. Let's try this, let me go back here, maybe I just needed to be on this page. So let's change, we'll go to free. Going to confirm. How do I get out of this?
That one was it, right? 52, 26. The last dot?
Come on. I don't think so that was two minutes ago. Thor, do you know if it'll only send one web hook. It should send both of these, right? Or should I disable this or something? I can check the logs, I hope this doesn't it's not sending anything. Like, it's not even getting the subscription. Am I on the wrong account? Corgi subscriptions. And these are the, these are the keys we use. So it wouldn't that's the right one. Successful requests. It's like, it's not doing anything. Can I find my customer object? Yeah, let me pull this off so I don't accidentally show a bunch of other people's email before I find mine. I don't even have what the heck? OK. I'm not in here. Which means, this has got to be something else. Stripe Jamstack subscriptions, maybe? Yeah, that's what it was. Yeah, it had it hooked up to the other thing. So let's get, let's get a web hook registered here. I'm going to add a new endpoint. I'm going to grab, nope this here. And we're going to do that subscription updated. This has been a whole lot of user error. So basically, what happened is I have multiple Stripe accounts.
And I thought we were using this one, but we actually moved it over to this one when we took the Stripe example live. So that's just me being in completely the wrong account.
Making too much money with Stripe, huh? That's the thing.
Let me get this web hook secret flipped over. And we'll try this one more time and it's going to work for us. I have swapped that now for the third time, the third time's a charm, is what I've been told. Let's trigger this thing. It's going to build in a few seconds. And as soon as it's built, we will give it another try. I do love filling out forms, Brandon. That's absolutely what I'm into. So that's done, so now, ooI'll going to go in here, I'm going to manage my subscription, I'm going to update my plan to the pro plan. Yes. We're going to go over here, we're going to look at our web hook attempts. And there it is. That's what it should have been.
And we got back 200 and it says it was received. And if I go into identity, I can see now we're pro. And if I go back to this page, we can see pro content, but not premium content. OK. So, it was a little bit of a long and windy road here. But right in the nick of time with two minutes left to spare, we managed to get fully functional, content gating with Contentful. So, this is really sweet. It took very little effort to get this up and running. And most of the hiccups we hit were me trying to understand how this thing was wired up and not actually there we go with the stampede. Most of the issues that we hit were not, were not issues with Contentful but more issues with, like, migrating a hard coded data over to the Contentful data. But this is pretty slick. Where should people go if they want to dig further into this? What do you recommend is the next step here?
Well, if people want to start playing around with Contentful, the Contentful/developers is the best place. If people are new to GraphQL/developers, so usually, you can go everywhere from here. And
And people on YouTube, GraphQL over the last week, I recorded. If you scroll down a little bit.
You scroll down, down, down, down, down. You see there, Learn GraphQL, that's what I recorded last week or something, it's Contentful, GraphQL and stuff.
If people have questions, I've seen rich text questions about Contentful rich text in the chat.
I'm available on Twitter for all things Contentful. So that usually works best.
Excellent. All right. Well, Stefan, thank you so much for taking the time today and for hanging out with us and teaching us about Contentful. Chat, thanks as always. And a quick shout out to our sponsors. We've had White Coat Captioning here doing live captioning for the whole show. We always want to make this show as accessible as possible to as many people. And that is made possible through the generous contributions of Nellify, Fauna, Sanity and auth 0. Thank you very much as always, it's always appreciated. With that, I think we're going to call this one done. Chat, we're going to raid. Stefan, thank you so much for hanging out. We will see you next time.