Latest Tutorials

Learn about the latest technologies from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Static Site Generation with Next.js and TypeScript (Part VI) - Client-Side Rendering

Disclaimer - Please read the fifth part of this blog post here before proceeding. It covers how to efficiently build a Next.js application with a single access token that can be used across all getStaticProps() and getStaticPath() functions. It also covers how to export a Next.js application to static HTML. If you just want to jump straight into this tutorial, then clone the project repository and install the dependencies. If all rendering happened on the client-side, then you end up with several problems. For example, suppose you build an application with Create React App . If you disable JavaScript in the browser and reload the application, then you will find the page void of content since React cannot run without JavaScript. Therefore, checking the DOM in the developer console, the <div id="root" /> element, where React renders all of the dynamic content, will be shown to be empty. There's also the possibility of the client running on an underpowered device, so rendering might take longer than expected. Or worse, the client has a poor network connection, so the application might have to wait longer for JavaScript bundles and other assets to be fully fetched before being able to render anything to the page. This is why it's important to not blindly render all content with only one rendering strategy. Rather, you should consider taking a hybrid approach when building an application. By having some content pre-rendered in advance via static-site generation (or server-side rendering) and having the remaining content rendered via client-side rendering, you can simultaneously deliver both a highly performant page and an enriching user experience. Thus far, every page of our Next.js application has been pre-rendered: the / and /types/:type pages. The initial HTML of the home page ( / ) contains the markup of the eight pet animal type cards that each allows users to navigate to a list of recently adopted pets. The initial HTML of each type page ( /types/:type ) contains the markup of the list of recently adopted pets. Once the page's initial HTML gets loaded and rendered, the browser hydrates the HTML. This breathes life into the page, giving it the ability to respond to user events and enabling features like client-side navigation and lazy-loading. Below, I'm going to show you how to render dynamic content on the client-side of a Next.js application with the useEffect Hook. When the user visits the /types/:type page, the list of pet animals available for adoption will be fetched and rendered on the client-side. By delegating the rendering of this list to the client-side, we reduce the likelihood of the list being outdated and including a pet that might have been adopted very recently. To get started, clone the project repository and install the dependencies. If you're coming from the fifth part of this tutorial series, then you can continue on from where the fifth part left off. Within the <TypePage /> component, let's define two state variables and their corresponding setter methods: ( pages/types/[type].tsx ) To fetch a list of adoptable pet animals, let's call a useEffect Hook that fetches this list upon the <TypePage /> component mounting to the DOM. The type prop is passed to the dependency array of this Hook because its id property is accessed within the Hook. ( pages/types/[type].tsx ) When you run the Next.js application in development mode ( make dev ) and visit the page http://localhost:3000/types/horse , you will encounter the following error message: Since the PETFINDER_ACCESS_TOKEN environment variable is not prefixed with NEXT_PUBLIC , the PETFINDER_ACCESS_TOKEN environment variable is not exposed to the browser. Therefore, we will need to fetch a new access token for sending requests to the Petfinder API on the client-side. ( pages/types/[type].tsx ) To communicate to users that the list of adoptable pet animals is being fetched from the Petfinder API, let's add a <Loader /> component and show it to the user anytime the page is updating this list. To the right of the animated spinner icon, the <Loader /> component shows a default "Loading ..." message. With the className and children props, you can override the <Loader /> component's styles and the default "Loading..." message respectively. ( components/LoadingSpinner.tsx ) Now let's add a new section to the <TypePage /> component that displays the list of adoptable pet animals. Additionally, let's update the <TypePage /> component accordingly so that the <Loader /> component is shown when the isUpdatingAdoptableListings flag is set to true . ( pages/types/[type].tsx ) When the Next.js application refreshes with these changes, fully reload the page. The list of recently adopted pet animals (above-the-fold content) instantly gets rendered since it was pre-rendered at build time. If you quickly scroll down the page, you can momentarily see the loader just before it disappears and watch as the client renders the fetched list of adoptable pet animals (below-the-fold content). Suppose the page is kept open for some time. The initial list of adoptable pet animals will become outdated. Let's give the user the ability to manually update this list by clicking an "Update Listings" button. ( components/UpdateButton.tsx ) When the user clicks on the button, and the handleOnClick method sets the isUpdating flag to true ... Within the <TypePage /> component, let's move the fetchAdoptableAnimals function out of the useEffect Hook so that it can also be called within the updateAdoptableListing() function, which gets passed to the handleOnClick prop of the <UpdateButton /> component. By wrapping the updateAdoptableListing() function in a useCallback Hook, the function is memoized and only gets recreated when the prop type changes, which should never change at any point during the lifetime of the <TypePage /> component. Therefore, anytime a state variable like adoptableAnimals or isUpdatingAdoptableListings gets updated and causes a re-render of the component, the fetchAdoptableAnimals() function will not be recreated. ( pages/types/[type].tsx ) Upon the initial page load, a simple animated spinner icon with the text "Loading..." is shown to the user as the client fetches for a list of adoptable pet animals. Then, anytime the user clicks on the "Update Listings" button to update the list of adoptable pet animals, an overlay with an animated spinner icon gets placed on top of the previous listings. This preserves the previous listings in case the client cannot update the list of adoptable pet animals (due to a network error, etc.). Let's make a few adjustments to the <TypePage /> component to ensure that the previous listings are kept in state when the client fails to update the list of adoptable pet animals. We add another state variable isUpdateFailed that's set to true only if an error was encountered when fetching an updated list of adoptable pet animals. When isUpdateFailed set to true , a generic error message "Uh Oh! We could not update the listings at this time. Please try again." is shown to the user. ( pages/types/[type].tsx ) If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here . Please stay tuned for future parts, which will cover topics like API routes and deployment of the Next.js application to Vercel! If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass :

Thumbnail Image of Tutorial Static Site Generation with Next.js and TypeScript (Part VI) - Client-Side Rendering

Static Site Generation with Next.js and TypeScript (Part III) - Optimizing Image Loading with Plaiceholder and BlurHash

Disclaimer - Please read the second part of this blog post here before proceeding. It explains the different data-fetching techniques that Next.js supports, and it guides you through the process of statically rendering a Next.js application page that fetches data at build time via the getStaticProps function. If you just want to jump straight into this tutorial, then clone the project repository and install the dependencies. Slow page loading times hurt the user experience. Anytime a user waits longer than a few seconds for a page's content to appear, they usually lose their patience and close out the page in frustration. A significant contributor to slow page loading times is image sizes. The larger an image is, the longer it takes the browser to download and render it to the page. One way to improve the perceived load time of images (and by extension, page) is to initially show a placeholder image to the user. This image should occupy the same space as the intended image to prevent cumulative layout shifting . Additionally, compared to the intended image, this image should be much smaller in size (at most, several KBs) so that it loads instantaneously (within the window of the page's first contentful paint). The placeholder image can be as simple as a single, solid color (e.g., Google Images or Medium) or as advanced as a blurred representation of the image (e.g., Unsplash). For Next.js application's, the <Image /> component from next/image augments the <img /> HTML element by automatically handling image optimizations, such as... These optimizations make the page's content immediately available to users to interact with. As a result, they help to improve not only a page's Core Web Vitals , but also the page's SEO ranking and user experience. Next.js <Image /> components support blurred placeholder images. To tell Next.js to load an image with a blurred placeholder image, you can either... If the Next.js application happens to load images from an external service like Unsplash, then for each image, would we need to manually create a blurred placeholder image, convert the blurred placeholder image into a Data URL and pass the Data URL to the blurDataUrl prop? With the Plaiceholder library, we can transform any image into a blurred placeholder image, regardless of where the image is hosted. The blurred placeholder image is a very low resolution version of the original image, and it can be embedded within the page via several methods: CSS, SVG, Base64 and Blurhash. Blurhash is an algorithm that encodes a blurred representation of an image as a small, compact string. Decoding this string yields the pixels that make up the blurred placeholder image. Using these pixels, you can render the blurred placeholder image within a <canvas /> element. Blurhash allows you to render a blurred placeholder image without ever having to send a network request to download it. Below, I'm going to show you how to improve image loading in a Next.js application with the Plaiceholder library and Blurhash. To get started, clone the project repository and install the dependencies. If you're coming from the second part of this tutorial series, then you can continue on from where the second part left off. Within the project directory, let's install several dependencies: Then, wrap the Next.js configuration with withPlaiceholder to extend the Next.js Webpack configuration so that Webpack excludes the sharp image processing library from the output bundle. ( next.config.js ) Specifying images.domains in the Next.js configuration restricts the domains that remote images can come from. This protects the application from malicious domains while still allowing Next.js to apply image optimizations to remote images. Currently, the images on the home page are high resolution and take a lot of time to be fully downloaded. In fact, without any image processing, some of the raw Unsplash images are as large as 20 MBs in size. However, by replacing these images with more optimized images, we can significantly improve the performance of the Next.js application. Using the <Image /> component from next/image and the Plaiceholder library, the home page will... These changes should shrink the bars in the developer console's network waterfall chart. To generate the blurred placeholder images, let's first modify the <HomePage /> component's getStaticProps() function so that these images get generated at build time. For each pet animal type, call the getPlaiceholder() method of the Plaiceholder library with one argument: a string that references the image. This string can be a relative path or an absolute URL. Here, the string will be an absolute URL since the images come directly from Unsplash. From the getPlaiceholder() method, we can destructure out any of the following: For the Next.js application, we will destructure out blurhash for a Blurhash-based blurred placeholder image and img for information about the actual image. ( pages/index.tsx ) Note #1 : The getPlaiceholder() method can accept a second, optional argument that lets you override the default placeholder size, configure the underlying Sharp library that Plaiceholder uses for image processing, etc. Check the Plaiceholder documentation here for more information. Note #2 : If you append the query parameters fm=blurhash&w=32 to the URL of an Unsplash image, then the response returned will be a Blurhash encoded string for that Unsplash image. Then, we need to update the shared/interfaces/petfinder.interface.ts file to account for the newly added properties ( blurhash and img ) on the AnimalType interface. ( shared/interfaces/petfinder.interface.ts ) After updating the shared/interfaces/petfinder.interface.ts file, let's replace the image that's set as a CSS background image in the <TypeCard /> component with two components: Run the Next.js application in development mode: When you visit the Next.js application in a browser, you will notice that the blurred placeholder images immediately appear. However, when the optimized images are loaded, you will notice that some of them are not aligned correctly within their parent containers: To fix this, let's use some of the <Image /> component's advanced props to instruct how an image should fit (via the objectFit prop) and be positioned (via the objectPosition prop) within its parent container. For these props to work, you must first set the <Image /> component's layout prop to fill so that the image can grow within its parent container (in both the x and y axes). The objectFit and objectPosition props correspond to the CSS properties object-fit and object-position . For objectFit , set it to cover so that the image occupies the entirety of the parent container's space while maintaining the image's aspect ratio. The outer portions of the image that fail to fit within the parent container will automatically be clipped out. For objectPosition , let's assign each pet animal type a unique objectPosition that positions the pet animal within the image to the center of the parent container. Any pet animal type that is not assigned a unique objectPosition will by default have objectPosition set to center . ( enums/index.ts ) ( pages/index.tsx ) ( shared/interfaces/petfinder.interface.ts ) When you re-run the Next.js application, you will notice that the images are now correctly aligned within their parent containers. When you check the terminal, you will encounter the following warning message being logged: Since we specified the layout prop as fill for the <Image /> component within the <TypeCard /> component, we tell the <Image /> component to stretch the image until it fills the parent element. Therefore, the dimensions of the image don't have to be specified, which means the height and width props should be omitted. Let's make the following changes to the <TypeCard /> component in components/TypeCard.tsx : Unsplash leverages Imgix to quickly process and deliver images to end users via a URL-based API . Based on the query parameters that you append to an Unsplash image's URL, you can apply various transformations to the image, such as face detection , focal point cropping and resizing . Given the enormous base dimensions of the Next.js application's Unsplash images (e.g., the dog image is 5184px x 3456px), we can resize these images to smaller sizes so that Plaiceholder can fetch them faster. Since the images occupy, at most, a parent container that's 160px x 160px, we can resize the initial Unsplash images so that they are, at most, 320px in width (and the height will be resized proportionally to this width based on the image's aspect ratio). Let's append the query parameter w=320 to the Unsplash image URLs in enums/index.ts . ( enums/index.ts ) When you re-run the Next.js application, you will notice that the Next.js application runs much faster now that Plaiceholder doesn't have to wait seconds to successfully fetch large Unsplash images. If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here . Proceed to the next part of this tutorial series to learn how to create pages for dynamic routes in Next.js. If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass .

Thumbnail Image of Tutorial Static Site Generation with Next.js and TypeScript (Part III) - Optimizing Image Loading with Plaiceholder and BlurHash

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More

Static Site Generation with Next.js and TypeScript (Part I) - Project Overview

Many of today's most popular web applications, such as G-Mail and Netflix, are single-page applications (SPAs). Single-page applications deliver highly engaging and exceptional user experiences by dynamically rendering content without fully reloading whole pages. However, because single-page applications generate content via client-side rendering, the content might not be completely rendered by the time a search engine (or bot) finishes crawling and indexing the page. When it reaches your application, a search engine will read the empty HTML shell (e.g., the HTML contains a <div id="root" /> in React) that most single-page applications start off with. For a smaller, client-side rendered application with fewer and smaller assets and data requirements, the application might have all the content rendered just in time for a search engine to crawl and index it. On the other hand, for a larger, client-side rendered application with many and larger assets and data requirements, the application needs a lot more time to download (and parse) all of these assets and fetch data from multiple API endpoints before rendering the content to the HTML shell. By then, the search engine might have already processed the page, regardless of the content's rendering status, and moved on to the next page. For sites that depend on being ranked at the top of a search engine's search results, such as news/media/blogging sites, the performance penalties and slower first contentful paint of client-side rendering may lower a site's ranking. This results in less traffic and business. Such sites should not client-side render entire pages worth of content, especially when the content infrequently (i.e., due to corrections or redactions) or never changes. Instead, these sites should serve the content already pre-generated as plain HTML. A common strategy for pre-generating content is static site generation . This strategy involves generating the content in advance (at build time) so that it is part of the initial HTML document sent back to the user's browser when the user first lands on the site. By exporting the application to static HTML, the content is created just once and reused on every request to the page. With the content made readily available in static HTML files, the client has much less work to perform. Similar to other static assets, these files can be cached and served by a CDN for quicker loading times. Once the browser loads the page, the content gets hydrated and maintains the same level of interactivity as if it was client-side rendered. Unlike Create React App , popular React frameworks like Gatsby and Next.js have first-class, built-in static site generation support for React applications. With the recent release of Next.js v12, Next.js applications build much faster with the new Rust compiler (compared to Babel, this compiler is 17x faster). Not only that, Next.js now lets you run code on incoming requests via middleware, and its APIs are compatible with React v18. In this multi-part tutorial, I'm going to show you how to... We will be building a simple, statically generated application that uses the Petfinder API to display pets available for adoption and recently adopted. All of the site's content will be pre-rendered in advance with the exception of pets available for adoption, which the user can update on the client-side. Home Page ( / ): Listings for Pet Animal Type ( /types/<type> ): Visit the live demo here: https://petfinder-nextjs.vercel.app/ To get started, initialize the project by creating its directory and package.json file. Note : If you want to skip these steps, then run the command npx create-next-app@latest --ts to automatically scafford a Next.js project with TypeScript. Then, proceed to the next section of this tutorial. Install the following dependencies and dev. dependencies: Add a .prettierrc file with an empty configuration object to accept Prettier's default settings. Add the following npm scripts to the package.json file: Here's what each script does: At the root of the project directory, create an empty TypeScript configuration file ( tsconfig.json ). By running next , Next.js automatically updates the empty tsconfig.json file with Next.js's default TypeScript configuration. ( tsconfig.json ) Note : If you come across the error message Error: > Couldn't find a `pages` directory. Please create one under the project root , then at the root of the project directory, create a new pages directory. Then, re-run the npm run dev command. Note : Double-check the configuration, and make sure that the moduleResolution compiler option is not missing and set to node . Otherwise, you will encounter the TypeScript error Cannot find module 'next' . Currently, this approach generates a TypeScript configuration that has the strict compiler option set to false . If you bootstrapped the project via the create-next-app CLI tool ( --ts / --typescript option), then the strict compiler option will be set to true . Let's set the strict compiler option to true so that TypeScript enforces stricter rules for type-checking. ( tsconfig.json ) Note : Setting strict to true automatically enables the following seven type-checking compiler options: noImplicitAny , noImplicitThis , alwaysStrict , strictBindCallApply , strictNullChecks , strictFunctionTypes and strictPropertyInitialization . Additionally, this command auto-generates a next-env.d.ts file at the root of the project directory. This file guarantees Next.js types are loaded by the TypeScript compiler. ( next-env.d.ts ) To further configure Next.js, create a next.config.js file at the root of the project directory. This file allows you to override some of Next.js's default configurations, such as the project's base Webpack configurations and mapping between incoming request paths and destination paths. For now, let's just opt-in to React's Strict Mode to spot out any potential problems, such as legacy API usage and unsafe lifecycles, in the application during development. ( next.config.js ) Similar to the tsconfig.json file, running next lint automatically installs the eslint and eslint-config-next development dependencies. Plus, it creates a new .eslintrc.json file with Next.js's default ESLint configuration. Note : When asked "How would you like to configure ESLint?" by the CLI, select the "Strict" option. ( eslintrc.json ) This application will be styled with utility CSS rules from the Tailwind CSS framework . If you are not concerned with how the application is styled, then you don't have to set up Tailwind CSS for the Next.js application and can proceed to the next section. Otherwise, follow the directions here to properly integrate in Tailwind CSS. To register for a Petfinder account, visit the Petfinder for Developers and click on "Sign Up" in the navigation bar. Follow the registration directions. Upon creating an account, go to https://www.petfinder.com/developers/ and click on the "Get an API Key" button. The form will prompt you for two pieces of information: "Application Name" and "Application URL" (at the minimum). For "Application Name," you can enter anything as the application name (e.g., "find-a-cute-pet" ). For "Application URL," you can enter https://<application_name>.vercel.app since the application will be deployed on Vercel by the end of this tutorial series. To see if an https://<application_name>.vercel.app URL is available, visit the URL in a browser. If Vercel returns a 404: NOT_FOUND page with the message <application_name>.vercel.app might be available. Click here to learn how to assign it to a project. , then the application name is likely available and can be used for completing the form. You can find the API key (passed as the client ID in the request payload to the POST https://api.petfinder.com/v2/oauth2/token endpoint) and secret (passed as the client secret in the request payload to the POST https://api.petfinder.com/v2/oauth2/token endpoint) under your account's developer settings. Here, you can track your API usage. Each account comes with a limit of 1000 daily requests and 50 requests per second. At the root of the project directory, create a .env file with the following environment variables: ( .env ) Replace the Xs with your account's unique client ID and secret. The home page features a grid of cards. Each card represents a pet animal type catalogued by the Petfinder API: These cards lead to pages that contain listings of pets recently adopted and available for adoption, along with a list of breeds associated with the pet animal type (e.g., Shiba Inu and Golden Retriever for dogs). Suppose you have to build this page with client-side rendering only. To fetch the types of animal pet from the Petfinder API, you must: Initially, upon visiting the page, the user would be presented with a loader as the client... Having to wait on the API to process these two requests before any content is shown on the page only adds to a user's frustrations. Wait times may even be worst if the API happens to be experiencing downtime or dealing with lots of traffic. You could store the access token in a cookie to avoid sending a request for a new access token each time the user loads the page. Still, you are left with sending a request for a list of types each time the user loads the page. Note : For stronger security (i.e., mitigate cross-site scripting by protecting the cookie from malicious JavaScript code), you would need a proxy backend system that interacts with the Petfinder API and sets an HttpOnly cookie with the access token on the client's browser after obtaining the token from the API. More on this later. This page serves as a perfect example for using static site generation over client-side rendering. The types returned from the API will very rarely change, so fetching the same data for each user is repetitive and unnecessary. Rather, just fetch this data once from the API, build the page using this data and serve up the content immediately. This way, the user does not have to wait on any outstanding requests to the API (since no requests will be sent) and can instantly engage with the content. With Next.js, we will leverage the getStaticProps function, which runs at build time on the server-side. Inside this function, we fetch data from the API and pass the data to the page component as props so that Next.js pre-renders the page at build time using the data returned by getStaticProps . Note : In development mode ( npm run dev ), getStaticProps gets invoked on every request. Previously, we created a pages directory. This directory contains all of the Next.js application's page components. Next.js's file-system based router maps page components to routes. For example, pages/index.tsx maps to / , pages/types/index.tsx maps to /types and pages/types/[type.tsx] maps to types/:type ( :type is a URL parameter). Now let's create four more directories: The Petfinder API documentation provides example responses for each of its endpoint. With these responses, we can define interfaces for the responses from the following endpoints: Create an interfaces directory within the shared directory. Inside of the interfaces directory, create a petfinder.interface.ts file. ( shared/interfaces/petfinder.interface.ts ) Note : This tutorial skips over endpoints related to organizations. Inside of the pages directory, create an index.tsx file, which corresponds to the home page at / . To build out the home page, we must first define the <HomePage /> page component's structure. ( pages/index.tsx ) Then, create the <TypeCardsGrid /> component, which renders a grid of cards (each represents a pet animal type). This component places the cards in a... ( components/TypeCardsGrid.tsx ) Then, create the <TypeCard /> component, which renders a card that represents a pet animal type. This component shows a generic picture of the pet animal type and a link to browse listings (recently adopted and available for adoption) of pet animals of this specific type. Note : The types returned from the Petfinder API do not have an id property, which serves as both a unique identifier and a URL slug (e.g., the ID of type "Small & Furry" is "small-furry"). In the next section, we will use the type name to create an ID. ( components/TypeCard.tsx ) Since the Petfinder API does not include an image for each pet animal type, we can define an enumeration ANIMAL_TYPES that supplements the data returned from the API with an Unsplash stock image for each pet animal type. To account for the images' different dimensions in the <AnimalCard /> component, we display the image as a background cover image of a <div /> and position the image such that the animal in the image appears in the center of a 10rem x 10rem circular mask. ( .enums/index.ts ) For users to browse the pet animals returned from the Petfinder API for a specific pet animal type, they can click on a card's "Browse Listings" link. The link is wrapped with a Next.js <Link /> component (from next/link ) to enable client-side navigation to the listings page. This kind of behavior can be found in single-page applications. When a user clicks on a card's "Browse Listings" link, the browser will render the listings page without having to reload the entire page. The <Link /> component wraps around an <a /> element, and the href gets passed to the component instead of the <a /> element. When built, the generated markup ends up being just the <a /> element, but having an href attribute and having the same behavior as a <Link /> component. To apply TailwindCSS styles throughout the entire application, we need to override the default <App /> component that Next.js uses to initialize pages. Next.js wraps every page component with the <App /> component. This is also useful for other reasons like... To override the default <App /> component, create an _app.tsx file within the pages directory. Inside of this file, import the styles/globals.css file that contains TailwindCSS directives, and define an <App /> component, like so: ( pages/_app.tsx ) The <App /> component takes two props: To verify that the homepage works and that the TailwindCSS styles have been applied correctly, let's run the application: Within a browser, visit localhost:3000 . Currently, the <TypeCardsGrid /> is not rendered since we have not yet fetched any pet animal types from the Petfinder API. If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here . Proceed to the next part of this tutorial series to learn how to fetch this data from the Petfinder API with the getStaticProps function. If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass :

Thumbnail Image of Tutorial Static Site Generation with Next.js and TypeScript (Part I) - Project Overview

RESTful API Documentation with Go and chi docgen Package

An important chore that gets neglected by developers is writing documentation for their RESTful APIs. Often, this task ends up being assigned lower priority than other tasks, such as building a new feature or modifying an existing feature. Although it delivers no immediate tangible value to end users like features, documentation produces intangible value for developers and their companies. By having detailed information about a RESTful API's endpoints, developers can quickly know how to obtain authorization for protected endpoints, access and interact with certain resources, format the data that's required in a request's body, etc. Ultimately, the value in documentation comes from increased developer productivity and saved development time. The more developers the documentation serves, the more value the documentation produces. Fortunately, you don't have to spend any time or effort to manually build and maintain documentation from scratch. There are open source tools like Swagger that automate the process of designing and generating RESTful API documentation for developers. These tools: Best of all, most of these tools only need, as input, the source code of the RESTful API or a JSON representation of the RESTful API that's compliant with the OpenAPI Specification . Or, these tools can get fed other representations of the RESTful API that are based on alternative API specifications like RAML ( R ESTful A PI M odeling L anguage). After generating the documentation, all that's left is to host and share the documentation. If you built your RESTful API with the Go chi library, then you can automatically generate routing documentation with one of its optional subpackages: docgen . Below, I'm going to show you how to leverage docgen to: To get started, clone the following repository to your local machine: This repository contains source code for a simple RESTful API built with Go and chi . The router uses a middleware stack that consists of the following middlewares: Additionally, render.SetContentType(render.ContentTypeJSON) forces the Content-Type of responses to be  application/json . Most of these middlewares are ported from Goji , a minimalistic web framework for Go, and they are built with native Go packages. The router defines several endpoints for a resource that represents posts: To start up the RESTful API, first install the project's dependencies. Then, compile and execute the code. docgen lets you print a list of available routes to STDOUT at runtime, like so: This gives an overview of the resources that can be accessed from this RESTful API. To log routing documentation to STDOUT, first add the github.com/go-chi/docgen dependency to the list of imported packages and dependencies in main.go , like so: Note #1 : If you encounter the error message cannot use r (variable of type *"github.com/go-chi/chi".Mux) as type "github.com/go-chi/chi/v5".Routes in argument to docgen.JSONRoutesDoc: , then you should change the imported dependency from github.com/go-chi/chi to "github.com/go-chi/chi/v5 . Note #2 : If you encounter the error message http: panic serving 127.0.0.1:50114: runtime error: slice bounds out of range [-1:] , then you should change the imported dependency from github.com/go-chi/chi/middleware to github.com/go-chi/chi/v5/middleware . Then, inside of the main() function, call the method docgen.PrintRoutes() after mounting the sub-router defined on the postsResource struct to the main router, like so: Stop the RESTful API service and re-run the following commands to install the newly added dependency ( github.com/go-chi/docgen ) and restart the RESTful API service: Now, the RESTful API's routes get logged to STDOUT. Since documentation should be shared with other developers, let's output the routing information to a pre-formatted Markdown document. Using the docgen.MarkdownRoutesDoc() method, docgen crawls the router to build a route tree. For each route and subroute, docgen collects: Then, using this tree, docgen structures and outputs the Markdown as a string. docgen will link each middleware and handler function to its implementation in the source code (the file and line where the implementation can be located). The docgen.MarkdownRoutesDoc() method accepts two arguments: Note : Any fields omitted from the struct are zero-valued. Unlike logging the route documentation to STDOUT, the Markdown should not be generated every time the RESTful API service starts up, especially if you want to write the stringified Markdown to a separate file. Instead, let's generate the Markdown only when the user passes a specific flag to the go run command. First, let's add the flag and errors packages to the list of imported packages and dependencies in main.go , like so: Just before the main() function, declare a string flag docs with a default value of an empty string (second argument) and a short description (third argument). Note : This flag will support three values: markdown , json and raml . -docs=markdown will output the routing information to a pre-formatted Markdown document, while -docs=json and -docs=raml will output JSON and RAML representations of the RESTful API respectively. We will implement the json and raml flag values in the next section of this tutorial. At the start of the main() function body, call the flag.Parse() method to parse the command-line flags into any flags defined before the main() function. At the end of the main() function body, after calling the docgen.PrintRoutes() method, check if the docs flag has been set to markdown . If so, then call the docgen.MarkdownRoutesDoc() method to generate the Markdown and write it to a routes.md file, like so: Note #1 : f, err := os.Create("routes.md") does not follow the same pattern as the other conditional statements (of having a declaration precede conditionals). Otherwise, f.Close() would cause the error undefined: f since f would only be scoped to its corresponding conditional statement's branches. For the statement to follow the same pattern as the other conditional statements, you would need to declare the variables f and err outside and replace the short declaration with a standard assignment. Note #2 : Even if the routes.md file does not exist, os.Remove will return an error remove routes.md: no such file or directory . Therefore, to prevent this error from causing the program to exit, check that the error is not an os.ErrNotExist error via the errors.Is() method. Now, add a new rule to the Makefile that compiles and executes the code with the -docs=markdown flag. ( Makefile ) Also, add the following code to the top of the Makefile so that docgen can detect the GOPATH environment variable when the go run command is executed via a make command. ( Makefile ) Note : Without this, you will encounter the error message ERROR: docgen: unable to determine your $GOPATH . When you run the make command with the target gen_docs_md , Go generates the routing documentation in Markdown and outputs it to a routes.md file: Below is what the Markdown file should look like. The bulleted middleware and handler functions link directly to their implementation in the source code (file and line). ( routes.md ) Having JSON and RAML representations of a RESTful API gives other software and tools the ability to understand the RESTful API's endpoints, middleware stack, etc. These representations can be consumed to create beautiful, custom UIs for displaying the documentation, build testing and scaffolding tools, etc. The APIs provided by docgen for generating JSON and RAML representations of the RESTful API are quite different. Therefore, let's start with the much simpler task of generating the JSON representation of the RESTful API. In the main() function, add another conditional branch ( else if statement) to the conditional statement *docs == "markdown" that checks if the docs flag is set to json . If so, then call the docgen.JSONRoutesDoc() method to generate the JSON representation and write it to a routes.json file, like so: Now, add a new rule to the Makefile that compiles and executes the code with the -docs=json flag. ( Makefile ) When you run the make command with the target gen_docs_json , Go generates the JSON representation and outputs it to a routes.json file: Below is what the JSON file should look like. The JSON data organizes the routes in a hierarchical order and contains information like the different HTTP methods allowed for a given route, the handler function and middleware stack that's called for a given endpoint, the location of the handler function in the source code, any comments written above the handler function's definition, etc. ( routes.json ) Note : Although the JSON representation of the RESTful API that's generated by docgen does not fully adhere to the OpenAPI specification , you can still apply a few transformations to make it compliant and work with tools like Swagger UI. For the RAML representation, we need to add the strings package and two more dependencies to the list of imported packages and dependencies in main.go : Then, in the main() function, add another conditional branch ( else if statement) to the conditional statement *docs == "markdown" that checks if the docs flag is set to raml . If so, then perform the following steps to generate the RAML representation of the RESTful API: Note : The fields in the raml.RAML struct come directly from the RAML specification, and they describe some of the basic aspects of the RESTful API like its title and version. This information ends up in the root section of the outputted RAML document. If you are not familiar with RAML, then please visit the RAML documentation . Now, add a new rule to the Makefile that compiles and executes the code with the -docs=raml flag. ( Makefile ) Stop the RESTful API service and install the newly added dependencies. When you run the make command with the target gen_docs_raml , Go generates the RAML representation and outputs it to a routes.raml file: Below is what the RAML file should look like. Notice how docgen organizes the routes in a similar hierarchical order like what's seen in the JSON representation of the RESTful API, but in a YAML-based format. To turn this RAML file into an interactive, documentation UI that can be shared with others, let's use the raml2html HTML documentation generator. You can install raml2html globally via npm and run its CLI tool to generate the documentation UI from RAML. However, if you do not want to install raml2html globally on your local machine, then you can also run a Dockerfile that containerizes raml2html . Run the following docker command to run the raml2html CLI tool in a new container, giving it the routes.raml file as an input and having it output a routes.raml.html file. Note : If you encounter the error message docker: Error response from daemon: Mounts denied: The path <full_project_directory_path> is not shared from the host and is not known to Docker. You can configure shared paths from Docker -> Preferences... -> Resources -> File Sharing. , then you should add the full path of the project directory (whatever gets printed by the pwd ) to the list of directories that can be bind mounted into Docker containers. After that's completed, open a new terminal session and re-run the docker command. Once the routes.raml.html file is generated (located in the root of the project directory), open this file in a browser to see the documentation UI. If you find yourself stuck at any point while working through this tutorial, then feel free to visit the main branch of this GitHub repository here for the code. Explore and try out other automated documentation solutions. If you want to learn more advanced back-end web development techniques with Go, then check out the Reliable Webservers with Go course by Nat Welch, a site reliability engineer at Time by Ping (and formerly a site reliability engineer at Google), and Steve McCarthy, a senior software engineer at Etsy.

Thumbnail Image of Tutorial RESTful API Documentation with Go and chi docgen Package

Integrating JWT Authentication with Go and chi jwtauth Middleware

Accessing an e-mail account anywhere in the world on any device requires authenticating yourself to prove the data associated with the account (e.g., e-mail address and inbox messages) actually belongs to you. Often, you must fill out a login form with credentials, such as an e-mail address and password, that uniquely identify your account. When you first create an account, you provide this information in a sign-up form. In some cases, the service sends either a confirmation e-mail or an SMS text message to ensure that you own the supplied e-mail address or phone number. Because it is highly likely that only you know the credentials to your account, authentication prevents unwanted actors from accessing your account and its data. Each time you log into your e-mail account and read your most recent unread messages, you, and like many other end users, don't think about how the service implements authentication to protect/secure your data and hide your activity history. You're busy, and you only want to spend a few minutes in your e-mail inbox before closing it out and resuming your day. For developers, the difficulty in implementing authentication comes from striking a balance between the user experience and the strength of the authentication. For example, a sign up form may prompt the user to enter a password that contains not only alphanumeric characters, but also must meet other requirements such as a minimum password length and containing punctuation marks. Asking for a stronger password decreases the likelihood of a malicious user correctly guessing it, but simultaneously, this password is increasingly more difficult for the user to remember. Keep in mind that poorly designed authentication can easily be bypassed and introduce more vulnerabilities into your application. In most cases, applications implement either session-based or token-based authentication to reliably verify a user's identity and persist authentication for subsequent page visits. Since Go is a popular choice for building server-side applications, Go's ecosystem offers many third-party packages for implementing these solutions into your applications. Below, I'm going to show you how to integrate JWT authentication within a Go and chi application with the chi jwtauth middleware. Let's imagine the following scenario. Within your e-mail inbox, you are asked to re-enter your e-mail address and password on every single action you take (e.g., opening an unread e-mail or switching to a different inbox tab) to continuously verify your identity. This implementation could be useful in the context of accidentally leaving your e-mail inbox open on a publicly-shared library computer when you have to step out to take a phone call. However, if the login credentials are sent over a non-HTTPS connection, then the login credentials are susceptible to a MITM (man-in-the-middle) attack and can be hijacked. Plus, it would result in a frustrating user experience and immediately drive users away to a different service. Traditionally, to persist authentication, an application establishes a session and saves an http-only cookie with this session's ID inside the user's browser. Usually, this session ID maps to the user's ID, which can then be used to fetch the user's information. If you have ever built an Express.js application with the authentication middleware library Passport and session middleware library express-session , then you are probably familiar with the connect.sid http-only cookie, which is a session ID cookie, and managing sessions with Redis . In Redis, the connect.sid cookie's corresponding key is the session ID (the substring proceeding s%3A and preceding the first dot of this cookie's value) prefixed with sess: , and its value contains information about the cookie and user authenticated by Passport. When a user sends an authentication request (via the standard username/password combination or an OAuth 2.0 provider such as Google / Facebook / Twitter ), Passport determines which of these authentication mechanisms ("strategies") to use to process the request. For example, if the user chooses to authenticate via Google, then Passport uses GoogleStrategy , like so: The done function supplies Passport with the authenticated user. To avoid exposing credentials in subsequent requests, the browser uses a unique cookie that identifies the user's session. Passport serializes the least amount of information that's required to map the user to the session. Often, the user's ID gets serialized. By serializing as little information as needed, this means there is less data stored in the user's session. Upon receiving a subsequent requests, Passport deserializes the user's ID (serialized via serializeUser ) into an object with the user's information, which allows it to be up to date with any recent changes. Whenever an Express.js route needs to access this information, it can via the req.user object. With session-based authentication, authentication is stateful because the server persists/tracks the session (either within the server's internal memory or an in-memory data store like Redis or Memcached). With token-based authentication, authentication is stateless . With tokens, nothing needs to be persisted on the server-side, and the server doesn't need to fetch the user's information on every subsequent request. One of the most popular token standards is JSON Web Token (JWT). JWTs are used for authorization, information exchange and verifying the user's authentication. Instead of creating a session, the server creates a cryptographically-signed JWT and saves an http-only cookie with this token inside of the user's browser, which allows the JWT to automatically be sent on every subsequent request. If the JWT is saved in plain memory, then it should be sent in the Authorization header using the Bearer authentication scheme ( Bearer <token> ). A JWT consists of three strings encoded in Base64URL : These strings are concatenated together (separated by dots) to form a token. Example : The following is a simple JWT, which follows the format <BASE64_URL_HEADER>.<BASE64_URL_PAYLOAD>.<BASE64_URL_SIGNATURE> : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Constructing JWTs is relatively straight-forward. Decoding a JWT is also relatively straight-forward. Try out different signing algorithms, adding scope: [ "admin", "user" ] to the payload or modifying the secret in the JWT debugger . Note : Since a JWT is digitally signed, its content is protected from tampering. Tampering invalidates the token. Having sensitive data in the payload or header requires the JWT to be encrypted. It is recommended to first sign the JWT and then encrypt it . Referring back to the previous Express.js and Passport example, we can remove both Redis, the session middleware and the serialization/deserialization logic (relies on sessions), and then add the Passport JWT strategy passport-jwt for authenticating with a JWT. We no longer have to devote any backend infrastructure/resources to managing sessions with the introduction of token-based authentication via JWT. This will significantly reduce the number of times we need to query the database for the user's information. Like any other authentication method, token-based authentication comes with its own set of unique problems. For example, when we store the token in a cookie, this cookie is sent on every request (bound to a single domain), even those that don't require the user to be authenticated. Although this cookie is stored with the HttpOnly attribute (inaccessible to JavaScript), it is still susceptible to a Cross-Site Request Forgery attack, which happens when a third-party website successfully sends a request to a service without the user's explicit consent due to cookies (those set by the service's server) being sent on all requests to that service's server. If you're running an online banking service, and one of your users is authenticated and visits a malicious website that sends the request POST https://examplebankingservice.com/transfer when they click on a harmless-looking button, then money will be transferred from the user's bank account since their valid token is sent with the request. To mitigate this vulnerability, set the token's cookie SameSite attribute ( sameSite: "lax" or sameSite: "strict" depending on your needs) and include a CSRF token specific to each user of your service in case of malicious subdomains. It should be set as a hidden form field in forms that send requests to protected endpoints upon being submitted, and your service should regenerate a new CSRF token for the user upon them logging in. This way, malicious websites cannot send requests to protected endpoints unless they also know that specific user's CSRF token. Note : By default, the latest versions of some modern browsers already treat cookies without the SameSite attribute as if this attribute was set to Lax . Setting the SameSite attribute of a cookie to Strict restricts a cookie to its originating website only and prevents cookies from being sent on any cross-site request or iframe. Setting the SameSite attribute of a cookie to Lax causes the same behavior as Strict , but relaxes the cross-site request restriction to target only POST requests. The alternative is to store the token in localStorage , but this is not recommended because localStorage is accessible by any JavaScript code running on your website. Therefore, it is susceptible to a Cross-Site Scripting attack, which allows unwanted JavaScript code to be injected into and executed within your website. Common attack vectors for XSS are passing unsanitized user input directly to eval and appending unsanitized HTML (contains a <script /> tag with malicious code). Unlike sessions, an individual JWT cannot be forcefully invalidated when security concerns arise. Rather, there are approaches that can be taken to invalidate a JWT, such as... Fortunately, supporting JWT authentication in a Go and chi application is made easy with the third-party jwtauth library. Similar to the Express.js and Passport example, jwtauth validates and extracts payload information from a JWT for route handlers via several pre-defined middleware handlers ( jwtauth.Verifier and jwtauth.Authenticator ) and context respectively. To demonstrate this, let's walkthrough a simple login flow: Inside of a Go file, scaffold out the routes using the chi router library. This application involves only four routes: ( main.go ) Let's think about these routes in-depth. When the user logs in, the navigation bar should no longer display a "Log In" link. Instead, the navigation bar should display the user's username as a link, which opens the user's "Profile" page when clicked, and a "Log Out" link. This means that all pages that display the navigation bar should be aware of whether or not the user is logged in, as well as the identity of the user. Let's group the GET / , GET /login and GET /profile endpoints together via the r.Group method, and then execute the middleware handler jwtauth.Verifier to seek, verify and validate the user's JWT token. This handler accepts a pointer to a JWTAuth struct, which is returned by the jwtauth.New method. Essentially, this method creates a factory for generating JWT tokens using a specified algorithm and secret (an additional key must be provided for RSA and ECDSA algorithms). The POST /login and POST /logout endpoints can be grouped together to establish them as routes that don't require a JWT token. Behind-the-scenes, jwtauth.Verifier automatically searches for a JWT token in an incoming request in the following order: Once the JWT token is verified, it is decoded and then set on the request context. This allows subsequent handlers to have direct access to the payload claims and the token itself. When the user submits a login form, their credentials are sent to the endpoint POST /login . It's corresponding route handler checks if the credentials are valid, and when they are, the handler generates a token that encodes parts of the user's information (i.e., their username) as payload claims via a MakeToken function and stores the token cookie within the user's browser, all before redirecting the user to their "Profile" page. Note : Underscores indicate placeholders for unused variables. For this simplicity's sake, we're going to accept any username and password combination as long as each is at least one character long. When the user logs out, this token cookie needs to be deleted. To delete this cookie, set its MaxAge to a value less than zero. After this cookie is deleted, redirect the user back to the homepage. Although the GET / , GET /login and GET /profile endpoints rely on the jwtauth.Verifier middleware, they each need to be grouped individually (not together) to add custom middleware to account for these scenarios: When rendering the webpages via data-driven templates , we need to extract the user's username from the JWT token's payload, which we encoded via the MakeToken function, to display it within the navigation bar. The payload's claims can be accessed from the request's context. Once the templates are parsed and prepared via the template.ParseFiles and template.Must methods respectively, apply these templates ( tmpl ) to the page data data via the ExecuteTemplate method. The second argument of ExecuteTemplate method names the root template that contains the other parsed templates (partials). The output is written to the ResponseWriter , which will render the result as a webpage. Note : If building a service, such as a RESTful API, that requires a 401 response to be returned on protected routes that can only be accessed by an authenticated user, use the jwtauth.Authenticator middleware. Finally, spin up the server by running the go run . command. Within a browser, visit the application at http://localhost:8080 and open the browser's developer console. Observe how the browser sets and unsets the cookie when you log in and out of the application, and watch as the user's username gets extracted from the JWT and displayed in the navigation bar. If you find yourself stuck at any point while working through this tutorial, then feel free to visit the main branch of this GitHub repository here for the code. Explore and try out other token standards for authentication. If you want to learn more advanced back-end web development techniques with Go, then check out the Reliable Webservers with Go course by Nat Welch, a site reliability engineer at Time by Ping (and formerly a site reliability engineer at Google), and Steve McCarthy, a senior software engineer at Etsy.

Thumbnail Image of Tutorial Integrating JWT Authentication with Go and chi jwtauth Middleware