Tag: JAMstack

Adding Dynamic And Async Functionality To JAMstack Sites

Jason Lengstorf:

Here’s an incomplete list of things that I’ve repeatedly heard people claim the JAMstack can’t handle that it definitely can:

  • Load data asynchronously
  • Handle processing files, such as manipulating images
  • Read from and write to a database
  • Handle user authentication and protect content behind a login

There is still a misconception that JAMstack = use a static site generator and that’s it, despite the fact that almost every article I’ve ever read about JAMstack gets into how it’s about pre-rendering what you can, and using client-side JavaScript and APIs to do the rest.

Phil laid that out very nicely for us recently.

This misconception seems very real to me. I hear it regularly. As I was writing this, I saw this question posted on Reddit.

Beginner question. Is JAM useful for applications or only for websites?

I’ll spare you from a speech about the uselessness of trying to distinguish between “apps” and “sites” but I think this helps make the point that there is plenty of confusion out there.


If you’re in a festive mood…

Tim Chase got creative and wrote this tongue-in-cheek poem. It’s obviously a joke but its assumption comes from the exact other angle, that JAMstack requires client-side JavaScript to do anything:

I do not like that stack that’s JAM
I do not like it, Sam-I-am.
I will not run it for a spell,
I will not use your GraphQL.
I will not run it over QUIC
No, Sam-I-am, it makes me sick.
Listen how it makes me cough
It does not work with JS off.

And Phil responded:

These thoughts make sense, I must agree
Except you really don’t need all three
It’s up to you. For you to choose.
JavaScript’s just an option you might use.
And if you do, success might be
From enhancing things progressively.

A JAMstack site might seem reliant
On doing everything in the client
In fact though, it depends on what
Requirements and use-cases you have got
The biggest key though, to remember
Is to serve things statically, and pre-render.

Direct Link to ArticlePermalink

The post Adding Dynamic And Async Functionality To JAMstack Sites appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,

What to Expect from the JAMstack in 2020

Brian Rinaldi interviewed a variety of folks, asking them the same questions about JAMstack development and the landscape recently:

  • Raymond Camden: I think we will see better competition from the bigger players.
  • Gift Egwuenu: I’m also looking forward to more job openings on the JAMstack.
  • Bryan Robinson: If you find yourself jumping through too many hoops, it might be time to explore a monolith architecture again.
  • Me: Blah blah blah, read the other ones from smart people 🙂
  • Tara Manicsic: there are more examples of enterprise applications creating more reliable user experiences, faster response times, and cutting their costs.

The post What to Expect from the JAMstack in 2020 appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

The JAMstack Landscape

It’s no big secret that Netlify invented the term JAMstack. While it’s possible to embrace the JAMstack without using Netlify, it’s notable that Netlify is at the very heart of the whole “JAMstack landscape.”

What does “JAMstack landscape” even mean? I like the term because it sets the stage that JAMstack isn’t just this one thing, but a way of building sites by piecing together a bunch of different ideas, the most important of which is static file hosting. It’s so important, I one jokingly called it SHAMstack. With static hosting at the core, it means that everything else you need to do can’t rely on hosted server-side languages, at least not right alongside the production website on the same server.

Without any server-side language available, how do you process your forms? Netlify does it for you. But there are a bunch of other services out there also.

What if you need to do your own special processing of data? That’s what cloud functions are for, and Netlify does it for you, but there are other services in the space.

What if you need a login system? Netlify does that along with other companies.

So that’s what “landscape” means. There are lots of companies, all involved in some slice of this pie. That’s something that makes Netlify so interesting. You can use them for just about every slice of that pie, and it’s a damn tasty pie (i.e. they do a great job of all the services they offer).

If it’s helpful to visualize, every time I hear JAMstack landscape mentioned, it’s always accompanied by this image from crv.com and this image from Redpoint.

The post The JAMstack Landscape appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

The Rising Complexity of JAMstack Sites and How to Manage Them

When you add anything with user-generated content or dynamic data to a static site, the complexity of the build process can become comparable to launching a monolithic CMS. How can we add rich content to static sites without stitching together multiple third-party services?

For people in the development community static site generators are a popular choice over traditional content management systems (CMS) like WordPress. By comparison static sites are usually lightweight, highly configurable, fast, easy to use and can be deployed almost anywhere.

With static websites, no code is generated on the server; we’ve replaced databases and server-side code with APIs and build processes.

This has become known as a JAMstack, which stands for JavaScript, APIs and Markup. I have a strong persuasion towards JAMstack sites because I feel more in control of the output than I do when working with the often large and monolithic CMSs I’ve sometimes had to use on client projects.

Despite my enthusiasm, I’m often disheartened by the steep complexity curve I typically encounter about halfway through a JAMstack project. Normally the first few weeks are incredibly liberating. It’s easy to get started, there is good visible progress, everything feels lean and fast. Over time, as more features are added, the build steps become more complex, multiple APIs are added, and suddenly everything feels slow. In other words, the development experience begins to suffer.

It usually looks something like this:

A hand-drawn chart showing complexity of a project over time. It shows a complexity curve that rises steeply at the end.

One of the reasons for this steep rise in complexity is there are limits to the type of data that markdown can easily represent. Relationships are one example where static sites struggle. Relationships between pages or collections of assets (such as an image gallery) can only be represented by markdown in inefficient ways. It requires significant preprocessing to resolve anything more complicated than a simple set of tags or categories. If you’ve ever had to do it, you will also know the authoring experience of managing relationships in markdown isn’t ideal.

User-generated content is another area that can cause a steep rise in the complexity of static sites. Adding features like comments, ratings, likes or any other kind of dynamic content will require third-party services — each has its own account that needs to be managed, not to mention that adding third-party scripts can have a negative impact of page performance.

If a service doesn’t match your specific requirements, sometimes it’s possible to cobble solutions together using generic platforms like Google Forms or AirTable.

The end result is we’ve outsourced the database, fragmented the content management experience and stitched together a bundle of compromises. That’s a stark contrast from the initial ease of setting up and deploying a JAMstack site.

Although this complexity curve is not unique to JAMstack projects, adding rich features to markdown-driven sites is far more difficult than we care to admit. What happened!? A lack of complexity was one of the reasons we favored JAMstack in the first place.

We did that thing that web developers do. We moved the complexity from one space into another and congratulated ourselves 😂. Not having complexity on the server-side is good for front-end performance, but there is little incentive to optimize any further once we do this. Ridiculous build times and complicated tool-chains have become an acceptable reality for modern front-end web development.

JAMstack Plus

Before I come across as sounding too critical, I should make it clear that I absolutely love static site generators. I think they are a perfect solution for many simple sites and you should still use them. However, I feel like a simple content management layer that I own and can configure is preferable to:

  • poor content management experiences,
  • complicated integration of third-party services, and
  • inefficient build processes.

I want to combine the benefits of a CMS with static site generators.

And it seems I’m not the only one who has reached this conclusion:

The solution doesn’t need to be another third-party service or require abandoning static sites entirely. You can use a personalized content management layer and unified API to enrich a static site. It might not be as hard as you think.

The first step is to create an API for your site. You can use any headless CMS, but the challenge I’ve had with many options is they make a lot of assumptions about the type of content you want. You might not want the CMS to manage pages and posts, but rather use it to store comments or images. I find this particularly difficult with WordPress. I often feel like I’m forcing a blogging platform to be just the service I need.

The new version of KeystoneJS (Keystone 5) is an excellent alternative to more opinionated content management systems. It’s made up of tiny independent components, so you only add the parts you need. This means it doesn’t feel like modifying a blogging platform. Instead it’s like creating a personalized mini-CMS and API to work specifically with your site.

I call this approach JAMstack Plus.

To help you get started with this idea I’ve created two projects:

  1. Supermaya, a starter kit for the static site generator Eleventy.
  2. Keystone JAMstack Plus, a blog enrichment platform.

Introducing Supermaya

The first project I want to share with you is Supermaya, an Eleventy starter kit designed to help add rich features to a blog or website without a complicated build process.

It comes with the all “blog standard” features including:

  • Posts and Pages
  • Pagination
  • Tags
  • RSS feed
  • Service worker
  • Lazy loading images
  • Critical CSS (if enabled)

It also has considerate and accessible markup. If deployed correctly, it should get full scores on a lighthouse audit out-of-the-box:

Supermaya scores 100% on Lighthouse tests.

I didn’t build Supermaya specifically as a platform to add user-generated content to static sites. Instead, I started it because I was not satisfied with the way existing static site generators integrate with other build tools. That’s why all the pre-processing steps in Supermaya are built into Eleveny itself. This includes the compilation of SCSS and JavaScript. Unifying the compilation steps eliminates the need for build tools like Grunt, Gulp or Webpack running in parallel.

After this, I realized the other reason for increasing complexity on JAMstack sites was integration with third-party services, usually for user-generated content. To solve this, Supermaya has optional tie-ins with a Keystone JAMstack Plus starter-kit, which makes it easier to add user-generated content and other rich features.

You can deploy both Keystone and Supermaya together and connect them at the same time by following the instructions during installation. This will deploy Keystone to Herouku and Supermaya to Netlify, as well as configure your admin user and API URL.

Rich features are added with progressive enhancement, so if the API cannot be reached or there is a server error, the site will continue to function without noticeable degradation or delays for users.

JAMstack Plus starter kit

The Keystone JAMstack Plus starter kit allows you to add rich features to a blog including:

  • Comments
  • Claps
  • Reading list, and
  • Logins

Just like Supermaya, it can be used on its own. After it’s deployed, you’ll get access to an admin interface that allows you to create and manage content. You’ll also get a GraphQL endpoint that can be connected to Supermaya.

It’s configured with the intention of being a headless CMS for user-generated content. It expects pages and posts to be managed by a static site generator. However, with a little work — and following the examples in Supermaya — you can connect any front-end to the GraphQL API.

I’d encourage you to modify the starter-kit: Add additional features or provide content for pages directly from Keystone. If you add features that could be used by the rest of the community contribute back to the starter-kit and we can make it easier for everyone to add rich features to their sites without the need for third-party services.

Note: The automatic deploy will deploy to a free instance of Heroku. This will sleep periodically if not used which can result in slow API response times after periods of inactivity. You can upgrade to a hobby instance to avoid this.

Consider owning your own data

JAMstack and servers are not incompatible. There’s always a server (usually multiple) — it’s just a question of who owns it. If you are using any kind of third-party service, the chances are they own your account information, your content and collect user data.

Sometimes this might be an acceptable compromise compared with the overhead of deploying and managing a back-end service, but when the complexity of stitching together several APIs becomes comparable to a CMS, I believe managing a tiny configurable service that you own, can provide a better experience for users, developers and content managers. It also provides a solid platform for websites to grow beyond purely static content into more complicated and varying types of applications.

I don’t think JAMstack should defined by pushing all the complexity into the front-end build process or by compromising on developer and user experience. Instead, I think JAMstack should focus on providing lean, configurable static front-ends. These can be connected to APIs to provide user-generated data and content management services. There is no reason not to own and manage these services, if it provides the best outcome.

CSS-Tricks

, , , , ,
[Top]

JAMstack, Fugu, and Houdini

What has me really excited about building websites recently is the fact that we, as front-end developers, have the power to do so much more. Only a few years ago, I would need a whole team of developers to accomplish what can now be done with just a few amazing tools.

Although the projects/tools/technologies are almost endless, in this article I’d like to talk about the top three that have me the most excited about building websites today, and for the future.

Serverless and the JAMstack

Serverless functions, which are really just server-side functions that you don’t host yourself, have been around for a few years, but they’ve really picked up in the past year or so. They allow us to host simple node functions that don’t require a permanent state and can be called from a frontend website the way we would call any other server-side API.

Serverless functions have really changed the game for me and I like to think that they did for frontend developers what sites like Squarespace did for non-developers. For the latter group, they no longer need a developer to build something simple like a portfolio website. For us frontend developers, we no longer need a backend developer to accomplish tasks like creating a contact form on a website. Things that really we should never have needed a whole API to do in the first place!

The popularity of serverless functions has led to the creation of a new tech stack: JavaScript, APIs, and Markup (JAMstack). I really love the concept of the JAMstack because it’s a move to more static, performant, websites, which I’m a big fan of. If you want to learn more about this stack, JAMstack_conf is a great conference to attend. I spoke at this year’s conference in San Francisco about using headless Chrome and Cloudinary to create progressively enhanced dynamic content (long title, I know). You can watch my talk below.

Project Fugu

Project Fugu is an initiative started by the Chromium team to bring to the web as many capabilities that are available to native applications as possible. A lot of these features are small and incremental, but the sum of the parts is going to make a huge change in the way we build progressive web applications.

One of the APIs I’m really looking forward to is the Native File System API, which will allow users of websites the ability to grant access to files on their system. A great use case for this would be Figma, the online interface design tool. Instead of having files “saved” online-only, they could directly work with files on your machine, the same way that native applications do!
Some other APIs I think are interesting are:

  • Wake Lock API – will allow websites to prevent the device from dimming or falling asleep
  • Contacts Picker API – will allow websites to access contacts from the user’s device
  • Get Installed Related Apps API – will allow websites to check if a native application is ins

You can view the full list of APIs.

CSS Houdini

Although Houdini isn’t exactly ready yet, it’s probably the technology I am most excited for as a lover of CSS because I believe it will be a true game-changer in how we build websites.

Houdini is a collection of APIs that exposes “hooks” into certain parts of the browser’s rendering engine. This gives us low-level access to the different stages that which CSS is applied, allowing us to essentially create our own CSS!

A great example of this is using the Layout Houdini API to create the infamous masonry layout as a new value for the display property. Once these APIs are out, the possibilities for what we will be able to create will be endless!

The post JAMstack, Fugu, and Houdini appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

JAMstack CMSs Have Finally Grown Up!

This article is based on Brian’s presentation at Connect.Tech 2019. Slides with speaker notes from that presentation are available to download.

In my experience, developers generally find the benefits of the JAMstack easy to comprehend. Sites are faster because the resources are static and served from a CDN. Sites are more secure because there is no framework, application server or database to compromise. Development and deployment can be optimized because all of the pieces that make up the stack are unbundled. And so on.

What can be more difficult for developers to comprehend are the trade-offs that this can often require for the folks who create and edit content. Traditional, monolithic content management systems have often been ridiculed by developers (yes, even WordPress) who became frustrated trying to bend the tool to their will in order to meet project requirements. But, until recently, the JAMstack largely just passed that burden onto the non-technical content creators and editors.

By developers, for developers

Static site generators (i.e. tools like Jekyll, Hugo and Gatsby) grew enormously in popularity in large part because developers adopted them for projects. They became common solutions for things like blogs, documentation or simple static pages. By and large, these were sites created by developers, maintained by developers and with the content primarily written and edited by developers.

When I first wrote about these tools in a report for O’Reilly in 2015, this is what I said:

Just in case this isn’t already clear, I want to emphasize that static site generators are built for developers. This starts with the development of the site all the way through to adding content. It’s unlikely that non-developers will feel comfortable writing in Markdown with YAML or JSON front matter, which is the metadata contained at the beginning of most static site engine content or files. Nor would non- technical users likely feel comfortable editing YAML or JSON data files.

—Me (Static Site Generators report for O’Reilly 2015)

When, two years later, I wrote a book for O’Reilly on the topic (with my friend Raymond Camden), not too much had changed. There were some tools at the very early stages, including Jekyll Admin and Netlify CMS, but they had not matured to a point that they could realistically compete with the sort of WYSIWYG tooling that content editors were used to in tools like WordPress.

The WordPress editor showing a field for the post title and a text area for the post content.
The WordPress editing experience

By contrast, the editing experience of static CMSs still required an understanding of Markdown and other markup (YAML, Liquid, etc.).

An editing screen in Netlify showing post fields on the left and a preview of the post on the right.
The Netlify CMS editing experience in 2017

Suffice it to say, whatever the technical merits of the architecture at the time, from a content editing standpoint, this was not a toolset that was ready for mainstream adoption.

The awkward teenage years

Over the ensuing two years, a combination of a couple of trends started to make the JAMstack a viable solution for mainstream content sites with non-technical editors. The first was that the static CMS matured into what we now generally refer to as git-based CMS solutions. The second was the rise of the headless, API-first CMS as a solution adopted by enterprises.

Let’s take a look at the first trend… well… first. Netlify CMS, an open-source project from Netlify, is an example of a git-based CMS. A git-based CMS doesn’t store your content, as a traditional CMS would, but it has tools that understand how to edit things like Markdown, YAML, JSON and other formats that make up a JAMstack site. This gives the content editors tools they feel comfortable with, but, behind the scenes, their content changes are simply committed back into the repository, forcing a rebuild of the site. While Netlify CMS is installed on the site itself, other popular git-based CMS options are web-based, including Forestry and DatoCMS.

An editing screen in Netlify from 2017 showing post fields on the left and a preview of the post on the right.
The current editing experience in Netlify CMS

The headless, API-first CMS functions much more like the editing experience in a traditional CMS. It not only offers tools for creating and editing content, but it stores that content. However, it makes that content available to the front end – any front-end – via an API. While not limited to JAMstack in any way, an API-first CMS works well with it because the creation and management of the content is separate from the display of that content on the front end. In addition, many API-first CMSs offer pre-built integrations with some of the most widely used static site generators. Popular API-first options include Contentful and Sanity.

The Contentful admin, showing post fields on the left and post settings on the right.
Contentful

HeadlessCMS.org is a site maintained by Netlify that has a comprehensive list of all the available tools, both git-based and API-first. For a good look at the differences, pros and cons between choosing a git-based versus an API-first CMS, check out this post by Bejamas.

Both git-based and API-first headless CMS options began to give non-technical content editors the tools they needed on the backend to create content. The awkwardness of these “teenage years” comes from the fact that the tooling is still disconnected from the frontend. This makes it difficult to see how changes you’ve made in the backend will impact the frontend until those changes are actually committed to the repo or pushed live via the API. Add in the time cost of a rebuild and you have a less than ideal editing experience where mistakes can more easily make it to the live site.

A Look at the future

So what does the future look like when the JAMstack CMS is finally grown up? Well, we got a good look at this year’s JAMstack_conf_sf. Coincidentally, there were two presentations demonstrating new tools that are bringing the content editing experience to the frontend, letting content editors see what they are changing, how their changes will look and how they will impact the layout of the site.

The first presentation was by Scott Gallant of Forestry. In it, he introduced an new open source projects from Forestry called TinaCMS that brings a WYSIWYG style content editing experience to the frontend of sites that use a git-based CMS and Gatsby or Next.js (both React-based tools).

Animated flow for editing a page on the front end with Tina CMS.
TinaCMS

The second presentation was by Ohad Eder-Pressman of Stackbit (full disclosure: I work as a Developer Advocate for Stackbit) that introduced an upcoming set of tools called Stackbit Live. Stackbit Live is designed to be CMS and static site generator agnostic, while still allowing on-page editing and previewing of a JAMstack site.

Animation of editing a page on the front end with Stackbit Love
Stackbit Live

What both these tools demonstrated is that we’re at a point where a “JAMStack + headless” architecture is a real alternative to a traditional CMS. I believe we’ve reached the tipping point whereby we’re no longer trading a great developer experience for an uncomfortable editing experience for content authors. By 2020, JAMstack CMS will officially be all grown up. 👩🏽‍🎓

The post JAMstack CMSs Have Finally Grown Up! appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

JAMstack Tools and The Spectrum of Classification

With the wonderful world of JAMstack getting big, all the categories of services and tools that help it along are as important as ever. There are static site generators, headless CMSs, and static file hosts.

I think those classifications are handy, and help conversations along. But there is a point where nuance is necessary and these classification buckets get a little leaky.

Note, these charts are just intended to paint a spectrum, not to be a comprehensive list of services.

Headless CMSs

A Headless CMS is a CMS that provides an admin area for creating and editing content, but offers no front-end to build the website from. All the content is accessed via APIs.

Imagine WordPress, which has an admin area, but it also has themes from which you build the website from on the server-side, with all kinds of PHP functions for you to use the content data. All that theming stuff is the “head”. So a headless CMS would be like WordPress with just the admin area. And indeed you can use it that way, as it offers APIs.

There is even more nuance here, as there are services that offer an admin area, but don’t actually store the data for you. Plus there is CMSs that are hosted for you, and CMSs where you have to bring your own hosting. Let’s have a peak.

Service Headless? Hosting Notes
Contentful Yes Cloud A classic headless CMS
Sanity JSON data structure, accessed via APIs, custom admin area is self-hosted
Cockpit Self Comes with admin UI
Strapi
KeystoneJS All code, not even an admin UI
WordPress Sorta – Usually used with head Self or Cloud Has a head, but you don’t have to use it, you choose to only use APIs to access content if you wish.
Drupal Self
CraftCMS Self Specifically has a headless mode and GraphQL API. Craft Cloud will bring a cloud-hosted headless varient
NetlifyCMS Sorta – Doesn’t actually store content, just helps edit it. GUI for Git-hosted Markdown
Forestry Cloud
Joomla No Self A classic headed CMS
Squarespace Cloud Site builder, meant to build hosted/headed sites
Wix

Static Site Hosts

This is tricky to talk about because literally, any web host will host static files, and probably do an OK job of it. I think it’s most useful to consider hosts that only do static hosting on purpose because it means they can optimize for that situation do other useful things.

Service Notes
Netlify The gold standard in static file hosts right now. Developer conviences galore.
Cloudflare Workers Sites CDN-first static file hosting alongside a cloud functions service.
Firebase Hosting Firebase is a whole suite of sub-products, but the hosting in particular is static and on a CDN.
GitHub Pages Static file host, but will also run Jekyll and other actions. Is not a CDN.
Neocities Static file host with online editor and community.
S3 Raw file storage. Can be configured to be a web host. Not a CDN unless you put CloudFront in front of it.
Bluehost Not really a static file host.
MediaTemple
Hostgator

Sometimes you’ll see people trying to use stuff like Dropbox or Google Drive to do static file hosting (for a website), but I’ve found these services generally ultimately don’t like that and prevent the use for that. If it works today, fine, but I wouldn’t count on any of them long term.

Static Site Generators

You would think this category would be straightforward, without much spectrum. A static site generator takes input and makes statically generated pages that can render without, say, needing to hit a database. But even here there is a spectrum.

The language the generator is in kinda matters. It affects speed. It affects installability on different local platforms. It affects your ability to write code to extend it and hack on it.

But perhaps more importantly, not all static site generators are only static site generators. Some can be run on the server as well, weirdly enough. And there are some that kinda look like static site generators, but are more correctly classified as flat-file CMSs.

Software Lang Notes
Jekyll Ruby One of the originals in this generation of static site generator.
Hugo Go Fast and beloved.
11ty Node Processes 11 different template languages out of the box.
Gatsby React Gatsby is truly a static site generator, but generally, the sites “hydrate” into SPAs, but remains static (nothing server-rendered).
Next Next can do static site generation, but it can also run live in Node and do server-side rendering on the fly (“Isomorphic JavaScript”).
Nuxt Vue Nuxt is the spirtiual companion to Next but in Vue. It also can either be staticly generator or run isomorphicly.
Kirby PHP Kirby runs from static files (no database), but isn’t really a static site as the pages are rendered by PHP.
Statamic Statamic is similar to Kirby in that static files are used for data but the page themselves are rendered by PHP.
Perch Just an example of a CMS that keeps data in a database and isn’t trying to be a static site generator at all.

The post JAMstack Tools and The Spectrum of Classification appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Random Notes from a JAMstack Roundtable

I hosted a JAMstack roundtable discussion at Web Unleashed this past weekend. Just a few random notes from that experience.

  • I was surprised at first that there really is confusion that the “M” in Jamstack stands for “Markdown” (the language that compiles to HTML) rather than “Markup” (the “M” in HTML, sometimes used interchangeably with HTML). It came up as legit confusion. Answer: Markdown isn’t required for JAMstack. The confusion comes from the idea that Markdown often goes hand-in-hand with static site generators which go hand-in-hand with JAMstack.
  • It occurred to me for the first time that every single site that is hosted on Netlify or GitHub Pages or an S3 bucket (“static hosting”) is JAMstack. SHAMstack, indeed! :). The static hosting (SH) part of JAMstack is, perhaps, the most important aspect.
  • A website that is a single index.html file with <div id="root"> and a bundle of JavaScript that client-site renders the rest of everything can be JAMstack. Assuming the data it needs is either baked in or coming from an API on some other server that isn’t the one that hosts that index.html file, it’s JAMstack.
  • There is a difference between technically JAMstack and spiritually JAMstack. The above is perhaps more technically and less spiritually. The latter wants you to pre-render more of the site than nothing.
  • Pre-rendering is nice because: it’s fast, it can be CDN-hosted, it’s secure, and it’s SEO-friendly. A lot of frameworks give it to you as part of what they do, so you might as well take advantage. Pre-rendering doesn’t mean static, JavaScript can still load and get fancy.
  • There is no denying that Static Site Generators and JAMstack are BFF. But JAMstack wants you to think bigger. What if you can’t render everything because you have 50,000 product pages and generation is too slow or otherwise impractical? No problem, you can prerender other pages but just a shell for the product pages and hit an API for product page data as needed. What if some pages just absolutely can’t be statically hosted? No problem, you can proxy the ones that can to a static server and leave the rest alone. Want to go all-in on the static hosting, but need servers for certain functionality? Consider serverless functions, which are sort of like the backend spiritual partner to static hosting.
  • People really want to know why. Why bother with this stuff at all? If you can build a site that does all the stuff you need with WordPress, why not just do that? I ended up defending my usage of WordPress from a feature perspective. If I had unlimited time and a fresh slate, even if I stay on WordPress as the CMS, I think I would likely head down a road of at least doing some pre-rendering if not totally de-coupling and building my own front-end and just using the data via APIs. But perhaps the most compelling answers to why come down to speed, security, and resiliency, all of which come along for the ride immediately when you go JAMstack. It provides a hell of a foundation to build on.

The post Random Notes from a JAMstack Roundtable appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Static First: Pre-Generated JAMstack Sites with Serverless Rendering as a Fallback

You might be seeing the term JAMstack popping up more and more frequently. I’ve been a fan of it as an approach for some time.

One of the principles of JAMstack is that of pre-rendering. In other words, it generates your site into a collection of static assets in advance, so that it can be served to your visitors with maximum speed and minimum overhead from a CDN or other optimized static hosting environment.

But if we are going to pre-generate our sites ahead of time, how do we make them feel dynamic? How do we build sites that need to change often? How do we work with things like user generated content?

As it happens, this can be a great use case for serverless functions. JAMstack and serverless are the best of friends. They complement each other wonderfully.

In this article, we’ll look at a pattern of using serverless functions as a fallback for pre-generated pages in a site that is comprised almost entirely of user generated content. We’ll use a technique of optimistic URL routing where the 404 page is a serverless function to add serverless rendering on the fly.

Buzzwordy? Perhaps. Effective? Most certainly!

You can go and have a play with the demo site to help you imagine this use case. But only if you promise to come back.

https://vlolly.net

Is that you? You came back? Great. Let’s dig in.

The idea behind this little example site is that it lets you create a nice, happy message and virtual pick-me-up to send to a friend. You can write a message, customize a lollipop (or a popsicle, for my American friends) and get a URL to share with your intended recipient. And just like that, you’ve brightened up their day. What’s not to love?

Traditionally, we’d build this site using some server-side scripting to handle the form submissions, add new lollies (our user generated content) to a database and generate a unique URL. Then we’d use some more server-side logic to parse requests for these pages, query the database to get the data needed to populate a page view, render it with a suitable template, and return it to the user.

That all seems logical.

But how much will it cost to scale?

Technical architects and tech leads often get this question when scoping a project. They need to plan, pay for, and provision enough horsepower in case of success.

This virtual lollipop site is no mere trinket. This thing is going to make me a gazillionaire due to all the positive messages we all want to send each other! Traffic levels are going to spike as the word gets out. I had better have a good strategy of ensuring that the servers can handle the hefty load. I might add some caching layers, some load balancers, and I’ll design my database and database servers to be able to share the load without groaning from the demand to make and serve all these lollies.

Except… I don’t know how to do that stuff.

And I don’t know how much it would cost to add that infrastructure and keep it all humming. It’s complicated.

This is why I love to simplify my hosting by pre-rendering as much as I can.

Serving static pages is significantly simpler and cheaper than serving pages dynamically from a web server which needs to perform some logic to generate views on demand for every visitor.

Since we are working with lots of user generated content, it still makes sense to use a database, but I’m not going to manage that myself. Instead, I’ll choose one of the many database options available as a service. And I’ll talk to it via its APIs.

I might choose Firebase, or MongoDB, or any number of others. Chris compiled a few of these on an excellent site about serverless resources which is well worth exploring.

In this case, I selected Fauna to use as my data store. Fauna has a nice API for stashing and querying data. It is a no-SQL flavored data store and gives me just what I need.

https://fauna.com

Critically, Fauna have made an entire business out of providing database services. They have the deep domain knowledge that I’ll never have. By using a database-as-a-service provider, I just inherited an expert data service team for my project, complete with high availability infrastructure, capacity and compliance peace of mind, skilled support engineers, and rich documentation.

Such are the advantages of using a third-party service like this rather than rolling your own.

Architecture TL;DR

I often find myself doodling the logical flow of things when I’m working on a proof of concept. Here’s my doodle for this site:

And a little explanation:

  1. A user creates a new lollipop by completing a regular old HTML form.
  2. The new content is saved in a database, and its submission triggers a new site generation and deployment.
  3. Once the site deployment is complete, the new lollipop will be available on a unique URL. It will be a static page served very rapidly from the CDN with no dependency on a database query or a server.
  4. Until the site generation is complete, any new lollipops will not be available as static pages. Unsuccessful requests for lollipop pages fall back to a page which dynamically generates the lollipop page by querying the database API on the fly.

This kind of approach, which first assumes static/pre-generated assets, only then falling back to a dynamic render when a static view is not available was usefully described by Markus Schork of Unilever as “Static First” which I rather like.

In a little more detail

You could just dive into the code for this site, which is open source and available for you to explore, or we could talk some more.

You want to dig in a little further, and explore the implementation of this example? OK, I’ll explain in some more details:

  • Getting data from the database to generate each page
  • Posting data to a database API with a serverless function
  • Triggering a full site re-generation
  • Rendering on demand when pages are yet to be generated

Generating pages from a database

In a moment, we’ll talk about how we post data into the database, but first, let’s assume that there are some entries in the database already. We are going to want to generate a site which includes a page for each and every one of those.

Static site generators are great at this. They chomp through data, apply it to templates, and output HTML files ready to be served. We could use any generator for this example. I chose Eleventy due to it’s relative simplicity and the speed of its site generation.

To feed Eleventy some data, we have a number of options. One is to give it some JavaScript which returns structured data. This is perfect for querying a database API.

Our Eleventy data file will look something like this:

// Set up a connection with the Fauna database. // Use an environment variable to authenticate // and get access to the database. const faunadb = require('faunadb'); const q = faunadb.query; const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET });  module.exports = () => {   return new Promise((resolve, reject) => {     // get the most recent 100,000 entries (for the sake of our example)     client.query(       q.Paginate(q.Match(q.Ref("indexes/all_lollies")),{size:100000})     ).then((response) => {       // get all data for each entry       const lollies = response.data;       const getAllDataQuery = lollies.map((ref) => {         return q.Get(ref);       });       return client.query(getAllDataQuery).then((ret) => {         // send the data back to Eleventy for use in the site build         resolve(ret);       });     }).catch((error) => {       console.log("error", error);       reject(error);     });   }) }

I named this file lollies.js which will make all the data it returns available to Eleventy in a collection called lollies.

We can now use that data in our templates. If you’d like to see the code which takes that and generates a page for each item, you can see it in the code repository.

Submitting and storing data without a server

When we create a new lolly page we need to capture user content in the database so that it can be used to populate a page at a given URL in the future. For this, we are using a traditional HTML form which posts data to a suitable form handler.

The form looks something like this (or see the full code in the repo):

<form name="new-lolly" action="/new" method="POST">    <!-- Default "flavors": 3 bands of colors with color pickers -->   <input type="color" id="flavourTop" name="flavourTop" value="#d52358" />   <input type="color" id="flavourMiddle" name="flavourMiddle" value="#e95946" />   <input type="color" id="flavourBottom" name="flavourBottom" value="#deaa43" />    <!-- Message fields -->   <label for="recipientName">To</label>   <input type="text" id="recipientName" name="recipientName" />    <label for="message">Say something nice</label>   <textarea name="message" id="message" cols="30" rows="10"></textarea>    <label for="sendersName">From</label>   <input type="text" id="sendersName" name="sendersName" />    <!-- A descriptive submit button -->   <input type="submit" value="Freeze this lolly and get a link">  </form>

We have no web servers in our hosting scenario, so we will need to devise somewhere to handle the HTTP posts being submitted from this form. This is a perfect use case for a serverless function. I’m using Netlify Functions for this. You could use AWS Lambda, Google Cloud, or Azure Functions if you prefer, but I like the simplicity of the workflow with Netlify Functions, and the fact that this will keep my serverless API and my UI all together in one code repository.

It is good practice to avoid leaking back-end implementation details into your front-end. A clear separation helps to keep things more portable and tidy. Take a look at the action attribute of the form element above. It posts data to a path on my site called /new which doesn’t really hint at what service this will be talking to.

We can use redirects to route that to any service we like. I’ll send it to a serverless function which I’ll be provisioning as part of this project, but it could easily be customized to send the data elsewhere if we wished. Netlify gives us a simple and highly optimized redirects engine which directs our traffic out at the CDN level, so users are very quickly routed to the correct place.

The redirect rule below (which lives in my project’s netlify.toml file) will proxy requests to /new through to a serverless function hosted by Netlify Functions called newLolly.js.

# resolve the "new" URL to a function [[redirects]]   from = "/new"   to = "/.netlify/functions/newLolly"   status = 200

Let’s look at that serverless function which:

  • stores the new data in the database,
  • creates a new URL for the new page and
  • redirects the user to the newly created page so that they can see the result.

First, we’ll require the various utilities we’ll need to parse the form data, connect to the Fauna database and create readably short unique IDs for new lollies.

const faunadb = require('faunadb');          // For accessing FaunaDB const shortid = require('shortid');          // Generate short unique URLs const querystring = require('querystring');  // Help us parse the form data  // First we set up a new connection with our database. // An environment variable helps us connect securely // to the correct database. const q = faunadb.query const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET })

Now we’ll add some code to the handle requests to the serverless function. The handler function will parse the request to get the data we need from the form submission, then generate a unique ID for the new lolly, and then create it as a new record in the database.

// Handle requests to our serverless function exports.handler = (event, context, callback) => {    // get the form data   const data = querystring.parse(event.body);   // add a unique path id. And make a note of it - we'll send the user to it later   const uniquePath = shortid.generate();   data.lollyPath = uniquePath;    // assemble the data ready to send to our database   const lolly = {     data: data   };    // Create the lolly entry in the fauna db   client.query(q.Create(q.Ref('classes/lollies'), lolly))     .then((response) => {       // Success! Redirect the user to the unique URL for this new lolly page       return callback(null, {         statusCode: 302,         headers: {           Location: `/lolly/$ {uniquePath}`,         }       });     }).catch((error) => {       console.log('error', error);       // Error! Return the error with statusCode 400       return callback(null, {         statusCode: 400,         body: JSON.stringify(error)       });     });  }

Let’s check our progress. We have a way to create new lolly pages in the database. And we’ve got an automated build which generates a page for every one of our lollies.

To ensure that there is a complete set of pre-generated pages for every lolly, we should trigger a rebuild whenever a new one is successfully added to the database. That is delightfully simple to do. Our build is already automated thanks to our static site generator. We just need a way to trigger it. With Netlify, we can define as many build hooks as we like. They are webhooks which will rebuild and deploy our site of they receive an HTTP POST request. Here’s the one I created in the site’s admin console in Netlify:

Netlify build hook

To regenerate the site, including a page for each lolly recorded in the database, we can make an HTTP POST request to this build hook as soon as we have saved our new data to the database.

This is the code to do that:

const axios = require('axios'); // Simplify making HTTP POST requests  // Trigger a new build to freeze this lolly forever axios.post('https://api.netlify.com/build_hooks/5d46fa20da4a1b70XXXXXXXXX') .then(function (response) {   // Report back in the serverless function's logs   console.log(response); }) .catch(function (error) {   // Describe any errors in the serverless function's logs   console.log(error); });

You can see it in context, added to the success handler for the database insertion in the full code.

This is all great if we are happy to wait for the build and deployment to complete before we share the URL of our new lolly with its intended recipient. But we are not a patient lot, and when we get that nice new URL for the lolly we just created, we’ll want to share it right away.

Sadly, if we hit that URL before the site has finished regenerating to include the new page, we’ll get a 404. But happily, we can use that 404 to our advantage.

Optimistic URL routing and serverless fallbacks

With custom 404 routing, we can choose to send every failed request for a lolly page to a page which will can look for the lolly data directly in the database. We could do that in with client-side JavaScript if we wanted, but even better would be to generate a ready-to-view page dynamically from a serverless function.

Here’s how:

Firstly, we need to tell all those hopeful requests for a lolly page that come back empty to go instead to our serverless function. We do that with another rule in our Netlify redirects configuration:

# unfound lollies should proxy to the API directly [[redirects]]   from = "/lolly/*"   to = "/.netlify/functions/showLolly?id=:splat"   status = 302

This rule will only be applied if the request for a lolly page did not find a static page ready to be served. It creates a temporary redirect (HTTP 302) to our serverless function, which looks something like this:

const faunadb = require('faunadb');                  // For accessing FaunaDB const pageTemplate = require('./lollyTemplate.js');  // A JS template litereal   // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET });  exports.handler = (event, context, callback) => {    // get the lolly ID from the request   const path = event.queryStringParameters.id.replace("/", "");    // find the lolly data in the DB   client.query(     q.Get(q.Match(q.Index("lolly_by_path"), path))   ).then((response) => {     // if found return a view     return callback(null, {       statusCode: 200,       body: pageTemplate(response.data)     });    }).catch((error) => {     // not found or an error, send the sad user to the generic error page     console.log('Error:', error);     return callback(null, {       body: JSON.stringify(error),       statusCode: 301,       headers: {         Location: `/melted/index.html`,       }     });   }); }

If a request for any other page (not within the /lolly/ path of the site) should 404, we won’t send that request to our serverless function to check for a lolly. We can just send the user directly to a 404 page. Our netlify.toml config lets us define as many level of 404 routing as we’d like, by adding fallback rules further down in the file. The first successful match in the file will be honored.

# unfound lollies should proxy to the API directly [[redirects]]   from = "/lolly/*"   to = "/.netlify/functions/showLolly?id=:splat"   status = 302  # Real 404s can just go directly here: [[redirects]]   from = "/*"   to = "/melted/index.html"   status = 404

And we’re done! We’ve now got a site which is static first, and which will try to render content on the fly with a serverless function if a URL has not yet been generated as a static file.

Pretty snappy!

Supporting larger scale

Our technique of triggering a build to regenerate the lollipop pages every single time a new entry is created might not be optimal forever. While it’s true that the automation of the build means it is trivial to redeploy the site, we might want to start throttling and optimizing things when we start to get very popular. (Which can only be a matter of time, right?)

That’s fine. Here are a couple of things to consider when we have very many pages to create, and more frequent additions to the database:

  • Instead of triggering a rebuild for each new entry, we could rebuild the site as a scheduled job. Perhaps this could happen once an hour or once a day.
  • If building once per day, we might decide to only generate the pages for new lollies submitted in the last day, and cache the pages generated each day for future use. This kind of logic in the build would help us support massive numbers of lolly pages without the build getting prohibitively long. But I’ll not go into intra-build caching here. If you are curious, you could ask about it over in the Netlify Community forum.

By combining both static, pre-generated assets, with serverless fallbacks which give dynamic rendering, we can satisfy a surprisingly broad set of use cases — all while avoiding the need to provision and maintain lots of dynamic infrastructure.

What other use cases might you be able to satisfy with this “static first” approach?

The post Static First: Pre-Generated JAMstack Sites with Serverless Rendering as a Fallback appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Hey, let’s create a functional calendar app with the JAMstack

Hey, let’s create a functional calendar app with the JAMstack

I’ve always wondered how dynamic scheduling worked so I decided to do extensive research, learn new things, and write about the technical part of the journey. It’s only fair to warn you: everything I cover here is three weeks of research condensed into a single article. Even though it’s beginner-friendly, it’s a healthy amount of reading. So, please, pull up a chair, sit down and let’s have an adventure.

My plan was to build something that looked like Google Calendar but only demonstrate three core features:

  1. List all existing events on a calendar
  2. Create new events
  3. Schedule and email notification based on the date chosen during creation. The schedule should run some code to email the user when the time is right.

Pretty, right? Make it to the end of the article, because this is what we’ll make.

A calendar month view with a pop-up form for creating a new event as an overlay.

The only knowledge I had about asking my code to run at a later or deferred time was CRON jobs. The easiest way to use a CRON job is to statically define a job in your code. This is ad hoc — statically means that I cannot simply schedule an event like Google Calendar and easily have it update my CRON code. If you are experienced with writing CRON triggers, you feel my pain. If you’re not, you are lucky you might never have to use CRON this way.

To elaborate more on my frustration, I needed to trigger a schedule based on a payload of HTTP requests. The dates and information about this schedule would be passed in through the HTTP request. This means there’s no way to know things like the scheduled date beforehand.

We (my colleagues and I) figured out a way to make this work and — with the help of Sarah Drasner’s article on Durable Functions — I understood what I needed learn (and unlearn for that matter). You will learn about everything I worked in this article, from event creation to email scheduling to calendar listings. Here is a video of the app in action:

You might notice the subtle delay. This has nothing to do with the execution timing of the schedule or running the code. I am testing with a free SendGrid account which I suspect have some form of latency. You can confirm this by testing the serverless function responsible without sending emails. You would notice that the code runs at exactly the scheduled time.

Tools and architecture

Here are the three fundamental units of this project:

  1. React Frontend: Calendar UI, including the UI to create, update or delete events.
  2. 8Base GraphQL: A back-end database layer for the app. This is where we will store, read and update our date from. The fun part is you won’t write any code for this back end.
  3. Durable Functions: Durable functions are kind of Serverless Functions that have the power of remembering their state from previous executions. This is what replaces CRON jobs and solves the ad hoc problem we described earlier.

See the Pen
durable-func1
by Chris Nwamba (@codebeast)
on CodePen.

The rest of this post will have three major sections based on the three units we saw above. We will take them one after the other, build them out, test them, and even deploy the work. Before we get on with that, let’s setup using a starter project I made to get us started.

Project Repo

Getting Started

You can set up this project in different ways — either as a full-stack project with the three units in one project or as a standalone project with each unit living in it’s own root. Well, I went with the first because it’s more concise, easier to teach, and manageable since it’s one project.

The app will be a create-react-app project and I made a starter for us to lower the barrier to set up. It comes with supplementary code and logic that we don’t need to explain since they are out of the scope of the article. The following are set up for us:

  1. Calendar component
  2. Modal and popover components for presenting event forms
  3. Event form component
  4. Some GraphQL logic to query and mutate data
  5. A Durable Serverless Function scaffold where we will write the schedulers

Tip: Each existing file that we care about has a comment block at the top of the document. The comment block tells you what is currently happening in the code file and a to-do section that describes what we are required to do next.

Start by cloning the starter form Github:

git clone -b starter --single-branch https://github.com/christiannwamba/calendar-app.git

Install the npm dependencies described in the root package.json file as well as the serverless package.json:

npm install

Orchestrated Durable Functions for scheduling

There are two words we need to get out of the way first before we can understand what this term is — orchestration and durable.

Orchestration was originally used to describe an assembly of well-coordinated events, actions, etc. It is heavily borrowed in computing to describe a smooth coordination of computer systems. The key word is coordinate. We need to put two or more units of a system together in a coordinated way.

Durable is used to describe anything that has the outstanding feature of lasting longer.

Put system coordination and long lasting together, and you get Durable Functions. This is the most powerful feature if Azure’s Serverless Function. Durable Functions based in what we now know have these two features:

  1. They can be used to assemble the execution of two or more functions and coordinate them so race conditions do not occur (orchestration).
  2. Durable Functions remember things. This is what makes it so powerful. It breaks the number one rule of HTTP: stateless. Durable functions keep their state intact no matter how long they have to wait. Create a schedule for 1,000,000 years into the future and a durable function will execute after one million years while remembering the parameters that were passed to it on the day of trigger. That means Durable Functions are stateful.

These durability features unlock a new realm of opportunities for serverless functions and that is why we are exploring one of those features today. I highly recommend Sarah’s article one more time for a visualized version of some of the possible use cases of Durable Functions.

I also made a visual representation of the behavior of the Durable Functions we will be writing today. Take this as an animated architectural diagram:

Shows the touch-points of a serverless system.

A data mutation from an external system (8Base) triggers the orchestration by calling the HTTP Trigger. The trigger then calls the orchestration function which schedules an event. When the time for execution is due, the orchestration function is called again but this time skips the orchestration and calls the activity function. The activity function is the action performer. This is the actual thing that happens e.g. “send email notification”.

Create orchestrated Durable Functions

Let me walk you through creating functions using VS Code. You need two things:

  1. An Azure account
  2. VS Code

Once you have both setup, you need to tie them together. You can do this using a VS Code extension and a Node CLI tool. Start with installing the CLItool:

 npm install -g azure-functions-core-tools  # OR  brew tap azure/functions brew install azure-functions-core-tools 

Next, install the Azure Function extension to have VS Code tied to Functions on Azure. You can read more about setting up Azure Functions from my previous article.


Now that you have all the setup done, let’s get into creating these functions. The functions we will be creating will map to the following folders.

Folder Function
schedule Durable HTTP Trigger
scheduleOrchestrator Durable Orchestration
sendEmail Durable Activity

Start with the trigger.

  1. Click on the Azure extension icon and follow the image below to create the schedule function

    Shows the interface steps going from Browse to JavaScript to Durable Functions HTTP start to naming the function schedule.
  2. Since this is the first function, we chose the folder icon to create a function project. The icon after that creates a single function (not a project).
  3. Click Browse and create a serverless folder inside the project. Select the new serverless folder.
  4. Select JavaScript as the language. If TypeScript (or any other language) is your jam, please feel free.
  5. Select Durable Functions HTTP starter. This is the trigger.
  6. Name the first function as schedule

Next, create the orchestrator. Instead of creating a function project, create a function instead.

  1. Click on the function icon:

  2. Select Durable Functions orchestrator.
  3. Give it a name, scheduleOrchestrator and hit Enter.
  4. You will be asked to select a storage account. Orchestrator uses storage to preserve the state of a function-in-process.
  5. Select a subscription in your Azure account. In my case, I chose the free trial subscription.
  6. Follow the few remaining steps to create a storage account.

Finally, repeat the previous step to create an Activity. This time, the following should be different:

  • Select Durable Functions activity.
  • Name it sendEmail.
  • No storage account will be needed.

Scheduling with a durable HTTP trigger

The code in serverless/schedule/index.js does not need to be touched. This is what it looks like originally when the function is scaffolded using VS Code or the CLI tool.

const df = require("durable-functions"); module.exports = async function (context, req) {   const client = df.getClient(context);   const instanceId = await client.startNew(req.params.functionName, undefined, req.body);   context.log(`Started orchestration with ID = '$ {instanceId}'.`);   return client.createCheckStatusResponse(context.bindingData.req, instanceId); };

What is happening here?

  1. We’re creating a durable function on the client side that is based on the context of the request.
  2. We’re calling the orchestrator using the client’s startNew() function. The orchestrator function name is passed as the first argument to startNew() via the params object. A req.body is also passed to startNew() as third argument which is forwarded to the orchestrator.
  3. Finally, we return a set of data that can be used to check the status of the orchestrator function, or even cancel the process before it’s complete.

The URL to call the above function would look like this:

http://localhost:7071/api/orchestrators/{functionName}

Where functionName is the name passed to startNew. In our case, it should be:

//localhost:7071/api/orchestrators/scheduleOrchestrator

It’s also good to know that you can change how this URL looks.

Orchestrating with a Durable Orchestrator

The HTTP trigger startNew call calls a function based on the name we pass to it. That name corresponds to the name of the function and folder that holds the orchestration logic. The serverless/scheduleOrchestrator/index.js file exports a Durable Function. Replace the content with the following:

const df = require("durable-functions"); module.exports = df.orchestrator(function* (context) {   const input = context.df.getInput()   // TODO -- 1      // TODO -- 2 });

The orchestrator function retrieves the request body from the HTTP trigger using context.df.getInput().

Replace TODO -- 1 with the following line of code which might happen to be the most significant thing in this entire demo:

yield context.df.createTimer(new Date(input.startAt))

What this line does use the Durable Function to create a timer based on the date passed in from the request body via the HTTP trigger.

When this function executes and gets here, it will trigger the timer and bail temporarily. When the schedule is due, it will come back, skip this line and call the following line which you should use in place of TODO -- 2.

return yield context.df.callActivity('sendEmail', input);

The function would call the activity function to send an email. We are also passing a payload as the second argument.

This is what the completed function would look like:

const df = require("durable-functions");  module.exports = df.orchestrator(function* (context) {   const input = context.df.getInput()        yield context.df.createTimer(new Date(input.startAt))        return yield context.df.callActivity('sendEmail', input); });

Sending email with a durable activity

When a schedule is due, the orchestrator comes back to call the activity. The activity file lives in serverless/sendEmail/index.js. Replace what’s in there with the following:

const sgMail = require('@sendgrid/mail'); sgMail.setApiKey(process.env['SENDGRID_API_KEY']);  module.exports = async function(context) {   // TODO -- 1   const msg = {}   // TODO -- 2   return msg; };

It currently imports SendGrid’s mailer and sets the API key. You can get an API Key by following these instructions.

I am setting the key in an environmental variable to keep my credentials safe. You can safely store yours the same way by creating a SENDGRID_API_KEY key in serverless/local.settings.json with your SendGrid key as the value:

{   "IsEncrypted": false,   "Values": {     "AzureWebJobsStorage": "<<AzureWebJobsStorage>",     "FUNCTIONS_WORKER_RUNTIME": "node",     "SENDGRID_API_KEY": "<<SENDGRID_API_KEY>"   } }

Replace TODO -- 1 with the following line:

const { email, title, startAt, description } = context.bindings.payload;

This pulls out the event information from the input from the orchestrator function. The input is attached to context.bindings. payload can be anything you name it so go to serverless/sendEmail/function.json and change the name value to payload:

{   "bindings": [     {       "name": "payload",       "type": "activityTrigger",       "direction": "in"     }   ] }

Next, update TODO -- 2 with the following block to send an email:

const msg = {   to: email,   from: { email: 'chris@codebeast.dev', name: 'Codebeast Calendar' },   subject: `Event: $ Hey, let’s create a functional calendar app with the JAMstack`,   html: `<h4>$ Hey, let’s create a functional calendar app with the JAMstack @ $ {startAt}</h4> <p>$ {description}</p>` }; sgMail.send(msg);  return msg;

Here is the complete version:

const sgMail = require('@sendgrid/mail'); sgMail.setApiKey(process.env['SENDGRID_API_KEY']);  module.exports = async function(context) {   const { email, title, startAt, description } = context.bindings.payload;   const msg = {     to: email,     from: { email: 'chris@codebeast.dev', name: 'Codebeast Calendar' },     subject: `Event: $ Hey, let’s create a functional calendar app with the JAMstack`,     html: `<h4>$ Hey, let’s create a functional calendar app with the JAMstack @ $ {startAt}</h4> <p>$ {description}</p>`   };   sgMail.send(msg);    return msg; };

Deploying functions to Azure

Deploying functions to Azure is easy. It’s merely a click away from the VS Code editor. Click on the circled icon to deploy and get a deploy URL:

Still with me this far in? You’re making great progress! It’s totally OK to take a break here, nap, stretch or get some rest. I definitely did while writing this post.

Data and GraphQL layer with 8Base

My easiest description and understanding of 8Base is “Firebase for GraphQL.” 8Base is a database layer for any kind of app you can think of and the most interesting aspect of it is that it’s based on GraphQL.

The best way to describe where 8Base fits in your stack is to paint a picture of a scenario.

Imagine you are a freelance developer with a small-to-medium scale contract to build an e-commerce store for a client. Your core skills are on the web so you are not very comfortable on the back end. though you can write a bit of Node.

Unfortunately, e-commerce requires managing inventories, order management, managing purchases, managing authentication and identity, etc. “Manage” at a fundamental level just means data CRUD and data access.

Instead of the redundant and boring process of creating, reading, updating, deleting, and managing access for entities in our backend code, what if we could describe these business requirements in a UI? What if we can create tables that allow us to configure CRUD operations, auth and access? What if we had such help and only focus on building frontend code and writing queries? Everything we just described is tackled by 8Base

Here is an architecture of a back-end-less app that relies on 8Base as it’s data layer:

Create an 8Base table for events storage and retrieval

The first thing we need to do before creating a table is to create an account. Once you have an account, create a workspace that holds all the tables and logic for a given project.

Next, create a table, name the table Events and fill out the table fields.

We need to configure access levels. Right now, there’s nothing to hide from each user, so we can just turn on all access to the Events table we created:

Setting up Auth is super simple with 8base because it integrates with Auth0. If you have entities that need to be protected or want to extend our example to use auth, please go wild.

Finally, grab your endpoint URL for use in the React app:

Testing GraphQL Queries and mutation in the playground

Just to be sure that we are ready to take the URL to the wild and start building the client, let’s first test the API with a GraphQL playground and see if the setup is fine. Click on the explorer.

Paste the following query in the editor.

query {   eventsList {     count     items {       id       title       startAt       endAt       description       allDay       email     }   } }

I created some test data through the 8base UI and I get the result back when I run they query:

You can explore the entire database using the schema document on the right end of the explore page.

Calendar and event form interface

The third (and last) unit of our project is the React App which builds the user interfaces. There are four major components making up the UI and they include:

  1. Calendar: A calendar UI that lists all the existing events
  2. Event Modal: A React modal that renders EventForm component to create a component
  3. Event Popover: Popover UI to read a single event, update event using EventForm or delete event
  4. Event Form: HTML form for creating new event

Before we dive right into the calendar component, we need to setup React Apollo client. The React Apollo provider empowers you with tools to query a GraphQL data source using React patterns. The original provider allows you to use higher order components or render props to query and mutate data. We will be using a wrapper to the original provider that allows you query and mutate using React Hooks.

In src/index.js, import the React Apollo Hooks and the 8base client in TODO -- 1:

import { ApolloProvider } from 'react-apollo-hooks'; import { EightBaseApolloClient } from '@8base/apollo-client';

At TODO -- 2, configure the client with the endpoint URL we got in the 8base setup stage:

const URI = 'https://api.8base.com/cjvuk51i0000701s0hvvcbnxg';  const apolloClient = new EightBaseApolloClient({   uri: URI,   withAuth: false });

Use this client to wrap the entire App tree with the provider on TODO -- 3:

ReactDOM.render(   <ApolloProvider client={apolloClient}>     <App />   </ApolloProvider>,   document.getElementById('root') );

Showing events on the calendar

The Calendar component is rendered inside the App component and the imports BigCalendar component from npm. Then :

  1. We render Calendar with a list of events.
  2. We give Calendar a custom popover (EventPopover) component that will be used to edit events.
  3. We render a modal (EventModal) that will be used to create new events.

The only thing we need to update is the list of events. Instead of using the static array of events, we want to query 8base for all store events.

Replace TODO -- 1 with the following line:

const { data, error, loading } = useQuery(EVENTS_QUERY);

Import the useQuery library from npm and the EVENTS_QUERY at the beginning of the file:

import { useQuery } from 'react-apollo-hooks'; import { EVENTS_QUERY } from '../../queries';

EVENTS_QUERY is exactly the same query we tested in 8base explorer. It lives in src/queries and looks like this:

export const EVENTS_QUERY = gql`   query {     eventsList {       count       items {         id         ...       }     }   } `;

Let’s add a simple error and loading handler on TODO -- 2:

if (error) return console.log(error);   if (loading)     return (       <div className="calendar">         <p>Loading...</p>       </div>     );

Notice that the Calendar component uses the EventPopover component to render a custom event. You can also observe that the Calendar component file renders EventModal as well. Both components have been setup for you, and their only responsibility is to render EventForm.

Create, update and delete events with the event form component

The component in src/components/Event/EventForm.js renders a form. The form is used to create, edit or delete an event. At TODO -- 1, import useCreateUpdateMutation and useDeleteMutation:

import {useCreateUpdateMutation, useDeleteMutation} from './eventMutationHooks'
  • useCreateUpdateMutation: This mutation either creates or updates an event depending on whether the event already existed.
  • useDeleteMutation: This mutation deletes an existing event.

A call to any of these functions returns another function. The function returned can then serve as an even handler.

Now, go ahead and replace TODO -- 2 with a call to both functions:

const createUpdateEvent = useCreateUpdateMutation(   payload,   event,   eventExists,   () => closeModal() ); const deleteEvent = useDeleteMutation(event, () => closeModal());

These are custom hooks I wrote to wrap the useMutation exposed by React Apollo Hooks. Each hook creates a mutation and passes mutation variable to the useMutation query. The blocks that look like the following in src/components/Event/eventMutationHooks.js are the most important parts:

useMutation(mutationType, {   variables: {     data   },   update: (cache, { data }) => {     const { eventsList } = cache.readQuery({       query: EVENTS_QUERY     });     cache.writeQuery({       query: EVENTS_QUERY,       data: {         eventsList: transformCacheUpdateData(eventsList, data)       }     });     //..   } });

Call the Durable Function HTTP trigger from 8Base

We have spent quite some time in building the serverless structure, data storage and UI layers of our calendar app. To recap, the UI sends data to 8base for storage, 8base saves data and triggers the Durable Function HTTP trigger, the HTTP trigger kicks in orchestration and the rest is history. Currently, we are saving data with mutation but we are not calling the serverless function anywhere in 8base.

8base allows you to write custom logic which is what makes it very powerful and extensible. Custom logic are simple functions that are called based on actions performed on the 8base database. For example, we can set up a logic to be called every time a mutation occurs on a table. Let’s create one that is called when an event is created.

Start by installing the 8base CLI:

npm install -g 8base

On the calendar app project run the following command to create a starter logic:

8base init 8base

8base init command creates a new 8base logic project. You can pass it a directory name which in this case we are naming the 8base logic folder, 8base — don’t get it twisted.

Trigger scheduling logic

Delete everything in 8base/src and create a triggerSchedule.js file in the src folder. Once you have done that, drop in the following into the file:

const fetch = require('node-fetch');  module.exports = async event => {   const res = await fetch('<HTTP Trigger URL>', {     method: 'POST',     body: JSON.stringify(event.data),     headers: { 'Content-Type': 'application/json' }   })   const json = await res.json();   console.log(event, json)   return json; };

The information about the GraphQL mutation is available on the event object as data.

Replace <HTTP Trigger URL> with the URL you got after deploying your function. You can get the URL by going to the function in your Azure URL and click “Copy URL.”

You also need to install the node-fetch module, which will grab the data from the API:

npm install --save node-fetch

8base logic configuration

The next thing to do is tell 8base what exact mutation or query that needs to trigger this logic. In our case, a create mutation on the Events table. You can describe this information in the 8base.yml file:

functions:   triggerSchedule:     handler:       code: src/triggerSchedule.js     type: trigger.after     operation: Events.create

In a sense, this is saying, when a create mutation happens on the Events table, please call src/triggerSchedule.js after the mutation has occurred.

We want to deploy all the things

Before anything can be deployed, we need to login into the 8Base account, which we can do via command line:

8base login

Then, let’s run the deploy command to send and set up the app logic in your workspace instance.

8base deploy

Testing the entire flow

To see the app in all its glory, click on one of the days of the calendar. You should get the event modal containing the form. Fill that out and put a future start date so we trigger a notification. Try a date more than 2-5 mins from the current time because I haven’t been able to trigger a notification any faster than that.

Yay, go check your email! The email should have arrived thanks to SendGrid. Now we have an app that allows us to create events and get notified with the details of the event submission.

The post Hey, let’s create a functional calendar app with the JAMstack appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]