One place for hosting & domains

      How To Use Themes in Gatsby


      The author selected the Internet Archive to receive a donation as part of the Write for DOnations program.

      Introduction

      Gatsby 1.0 was released in 2017, and since then has continued to release new features, while maintaining its high customizability as a static site generator. Added features have taken the form of plugins, new APIs, utilities, and configuration options. Each of these features can be individually used and customized or combined together to apply specific use cases. However, since many sites benefit from the same combinations of features and settings, Gatsby introduced Gatsby themes.

      Themes in Gatsby refer specifically to plugins that act as a collection of configuration options, functionality, and/or user interface (UI) elements. Separating out shared features into maintained themes makes keeping your site up-to-date easier, and also lets you spend less time configuring your site and more time developing your content.

      In this tutorial, you will install, configure, and use a Gatsby theme for publishing blog posts: gatsby-theme-blog. This plugin bundles multiple features, such as MDX support and code highlighting, into one convenient package. Throughout the tutorial, you will follow the process of using this specific Gatsby theme, which you can then apply to themes in general.

      Each step will take you through an important part of using Gatsby themes, and by the end you will have seen how this process can be applied to any Gatsby theme that you might want to use in the future.

      Prerequisites

      Before starting, here are a few things you will need:

      • A local installation of Node.js for running Gatsby and building your site. The installation procedure varies by operating system, but DigitalOcean has guides for Ubuntu 20.04 and macOS, and you can always find the latest release on the official Node.js download page.
      • Some familiarity with JavaScript, for working in Gatsby. The JavaScript language is an expansive topic, but a good starting point is our How To Code in JavaScript series.
      • A new Gatsby project, scaffolded from gatsby-starter-default. To build a new Gatsby project from scratch, you can refer to Step 1 of the How To Set Up Your First Gatsby Website tutorial.

      This tutorial was tested on Node.js v14.16.1, npm v6.14.12, Gatsby v3.11.1, and gatsby-theme-blog v3.0.0.

      Step 1 — Finding and Installing a Theme

      Although this tutorial will walk you through using a specific theme, gatsby-theme-blog, each step also applies conceptually to Gatsby themes in general. This applies to this first step as well: finding a theme you wish to use and installing it with npm.

      Gatsby provides guidance to plugin and theme authors on how they should publish their Gatsby packages, which makes it easier to find a theme to fit your needs. Theme developers are instructed to tag their theme packages with both gatsby and gatsby-theme as keywords, which are then scanned by package registries (where the files are actually hosted) and made searchable.

      This tutorial follows the use-case of building a Gatsby-powered site with a blog sub-section. As the developer, you are looking for a theme to add support for MDX, code highlighting, and more. Although Gatsby has its own plugin browser, it actually pulls its listings from the npm registry, so your first step is to start your search directly in the npm registry search engine. By using the search input of blog keywords:gatsby-theme, you will limit your results to only those plugins that have the gatsby-theme keyword, as shown in the following screenshot:

      npm results page for the

      In this tutorial, you will be using gatsby-theme-blog, so select that package. With gatsby-theme-blog selected as the theme you are going to install, the next part of this step is to actually install it, along with its dependencies. Navigate to your existing Gatsby project and run the following command in the directory:

      • npm install gatsby-theme-blog

      After the dependencies have finished installing, you will see a message similar to the following:

      Output

      ... + gatsby-theme-blog@3.0.0 added 262 packages from 181 contributors and audited 2391 packages in 49.706s

      Now that you have installed the theme and its dependencies into your project, it is time to move on to loading and configuring the theme within your Gatsby project.

      Step 2 — Loading and Configuring the Theme

      Now that your theme is installed, you can start to actually use it across your site and modify it to your needs. In Gatsby, the main theme initialization and configuration is done by editing the root configuration file, gatsby-config.js. In this step, you will edit the configuration file to load in your theme with your desired options.

      Open the gatsby-config.js configuration file in your preferred editor, then add the following:

      gatsby-config.js

      module.exports = {
        plugins: [
          ...
          `gatsby-plugin-image`,
          {
            resolve: 'gatsby-theme-blog',
            options: {
              basePath: '/posts',
              contentPath: `md/posts`,
            }
          },
          `gatsby-transformer-sharp`,
          ...
        ]
      }
      

      In this configuration code, there are two important settings that you are using custom values for. The theme uses the basePath option to set the blog’s URL, and contentPath tells the theme where to find the Markdown files to publish as blog posts. Using a value of md/posts for contentPath means that your Markdown files must reside in the md/posts directory.

      Once you have added this code, save your configuration file.

      The gatsby-theme-blog theme offers additional settings, documented in the gatsby-theme-blog README file. Since each Gatsby theme is different, the most important part of this step is referring to the documentation of your selected theme and following the exact guidance provided in it.

      You have now set up the theme to be loaded and configured to your liking through the Gatsby configuration file. The next step is to explore some of the new functionality that it adds to your site, testing along the way.

      Step 3 — Testing Functionality

      As your theme is now installed, configured, and loaded, you can now implement it in your site. This step covers how to try out some of the new functionality bundled with the theme and preview your results.

      You will test out the MDX, code highlighting, and Markdown processing support of gatsby-theme-blog with a new blog post file. First, you will need to make the directory to hold the files, which needs to correspond with the contentPath setting of md/posts you used in step #2. You can either make this directory manually in your file browser or make it in your terminal by running this command in the root of your Gatsby project:

      Next, create an empty MDX file, my-first-post.mdx, that will contain your new post content. Again, you can create this manually or in the terminal:

      • touch ./md/posts/my-first-post.mdx

      Now open up the empty MDX file and add in the following code:

      md/posts/my-first-post.mdx

      ---
      title: Learning Gatsby Themes and Trying MDX
      slug: /posts/gatsby-theme-learning
      date: 2021-08-16
      excerpt: A post about learning Gatsby themes and trying out some MDX.
      ---
      
      ## Welcome!
      
      This is a post where I plan to share my journey learning Gatsby Themes, and to try out some MDX.
      
      ## Resources
      
      <ul>
      {[
          {
              link: 'https://www.gatsbyjs.com/',
              text: 'Gatsby Website',
              note: 'Official Website for Gatsby'
          },
          {
              link: 'https://www.gatsbyjs.com/docs/themes/',
              text: 'Gatsby Theme Documentation',
              note: 'Documentation for Gatsby Theme usage and development'
          },
          {
              link: 'https://www.digitalocean.com/community/tutorial_series/how-to-create-static-web-sites-with-gatsby-js',
              text: 'DigitalOcean - "How To Create Static Web Sites with Gatsby.js"',
              note: 'A DigitalOcean tutorial series on using Gatsby JS'
          }
      ].map(item => (
          <li key={item.link}>
              <a href={item.link} target="_blank">{item.text}</a>
              <ul>
                  <li>{item.note}</li>
              </ul>
          </li>
      ))}
      </ul>
      
      ## Code Sample
      
      To try out code highlighting in this theme, here is a snippet of JavaScript code. This code won't run in your browser; it is for visual use only.
      

      At the top of the file, the section enclosed by --- is a set of key-value pairs called frontmatter. Not every theme uses the same keys, and the ones you are using in your post have been carefully selected out of the keys used by gatsby-theme-blog. You have defined a custom title, slug (the URL path), publication date, and excerpt (preview text to display on the /posts listing page).

      All the text that follows the frontmatter becomes the body of the post, starting with your Welcome! section. The two hash symbols (##) before the heading text tell Markdown this is a level-2 heading, which is used for the Resources section as well.

      In the Resources section, you have your first usage of what makes MDX different from regular Markdown: the use of React’s JSX syntax to embed React components that get merged with your Markdown and rendered into a single page. In your post, you are using JSX to turn a collection of resources about Gatsby into an HTML list of links.

      Finally, to test out the code syntax highlighting feature bundled with gatsby-theme-blog, add a Markdown Fenced Code Block at the end of the file:

      md/posts/my-first-post.mdx

          ```js
          function saySomething(name) {
              console.log(`Hello ${name}!`);
              console.log(`Isn't learning about Gatsby fun?!`);
          }
          saySomething('Arthur');
          ```
      

      This uses triple backticks to indicate the boundaries of where the code starts and stops.

      Save and close the MDX file, as you are done editing the new post.

      To see this post as a viewer would, testing all the features of your theme plugin, run the following command:

      Once ready, the Gatsby CLI will prompt you to open your project in your web browser, which you can do by navigating to localhost:8000. To see the new blog listing page, visit localhost:8000/posts, and to see this specific new post, navigate to localhost:8000/posts/gatsby-theme-learning/. The blog post will look like the following:

      The tutorial's MDX blog post, built with Gatsby and rendered in the browser.

      You have just tested out some of the functionality that your newly installed theme provides, and viewed the results of your efforts in a web browser. For many users, this might cover all their needs, but for a higher level of customization, the next step explores a Gatsby concept called shadowing that lets you override pieces of a theme.

      Step 4 — Using Shadowing (Optional)

      At this point in the tutorial, you have already installed and configured a third-party theme within Gatsby. Configuration took place in gatsby-config.js, and was limited to the options that the theme publisher chose to make customizable. If you needed to customize a theme beyond these options, you would use a Gatsby concept called shadowing, which you will do in this step.

      The term shadowing refers to the practice of overriding or extending a built-in theme file with your own modifications. For those familiar with WordPress, this is similar to the concept of a child theme.

      With Gatsby themes, any file in the theme’s source code can be shadowed, from methods that affect Gatsby nodes and file generation to UI elements and layouts. For your blog, you will shadow a React component file named bio-content.js to customize how your blog bio appears below each post. By shadowing this one file, you will affect the appearance of every blog post that goes through the gatsby-theme-blog plugin.

      The first step of shadowing is to create a folder in your src directory with the same exact name as the theme plugin you want to shadow. You can do this manually, or with the terminal:

      • mkdir src/gatsby-theme-blog

      For any particular file in the theme that you want to shadow, the next step is to create a file with that exact same name and in the same directory structure as in the theme. Because you are only going to slightly modify the existing bio component, you can save some time by copying the existing file over as a starting point, with the cp (copy) command:

      • mkdir -p src/gatsby-theme-blog/components
      • cp node_modules/gatsby-theme-blog/src/components/bio-content.js src/gatsby-theme-blog/components/bio-content.js

      Now open the newly copied file, and make the following modifications:

      src/gatsby-theme-blog/components/bio-content.js

      import React, { Fragment } from "react"
      
      const BioContent = () => (
        <Fragment>
          <p>Content by DigitalOcean</p>
          <p>License Info:</p>
          <p
            style={{
              margin: "10px 20px",
              padding: 8,
              backgroundColor: "#0069ff",
              color: "white",
              borderRadius: 12,
            }}
          >
            This work is licensed under a Creative Commons
            Attribution-NonCommercial-ShareAlike 4.0 International License.
          </p>
        </Fragment>
      )
      
      export default BioContent
      

      Save and close this file, as you are now done editing the shadow file.

      In this file, you have shadowed the original bio-content.js file, replacing the placeholder text with an author name and license information. You have done so by replacing the JSX returned by the BioContent React component. The style={{}} code is an example of inline CSS, which you have used to add some color and spacing to the license callout.

      By running npm run develop again in your terminal, you will launch the Gatsby development server and can preview the changes across all your blog posts:

      Screenshot showing the new bio-content that lists

      By using Gatsby shadowing, you have just modified a third-party Gatsby theme beyond the standard configuration, mixing in your own custom overrides and extensions.

      Conclusion

      By following the steps in this tutorial, you now have a Gatsby site that uses a published theme to pull in multiple components and pieces of bundled functionality as a single dependency. Thanks to this packaged functionality, your site is now easier to update and can contain much less configuration and setup code, despite having additional features. You have also learned how to use shadowing to customize Gatsby themes at a deeper file-based level, unlocking another layer of extensibility.

      Although this tutorial covers a specific theme, the concepts and approaches it outlines apply to Gatsby themes in general. For more on Gatsby themes, take a look at the official Gatsby documentation. If you would like to read more about Gatsby, check out the rest of the How To Create Static Web Sites with Gatsby.js series.



      Source link

      How To Install Ruby on Rails with rbenv on Ubuntu 20.04


      Introduction

      Ruby on Rails is one of the most popular application stacks for developers looking to create sites and web apps. The Ruby programming language, combined with the Rails development framework, allows you to build and deploy scalable apps quickly.

      You can install Ruby and Rails with the command line tool rbenv. Using rbenv provides you with a solid environment for developing your Ruby on Rails applications and allows you to switch between Ruby versions, keeping your entire team on the same version. rbenv also provides support for specifying application-specific versions of Ruby, allows you to change the global Ruby for each user, and the option to use an environment variable to override the Ruby version.

      In this tutorial, we will guide you through the Ruby and Rails installation processes with rbenv and gem. First, you’ll install the appropriate packages to install rbenv and then Ruby. After, you’ll install the ruby-build plugin so that you can install available versions of Ruby. Last, you’ll use gem to install Rails and can then use Ruby on Rails to begin your web development. We will also provide steps on how to check if your rbenv version is up-to-date, and how to uninstall Ruby versions and rbenv.

      Prerequisites

      To follow this tutorial, you need:

      Step 1 – Install rbenv and Dependencies

      Ruby relies on several packages that you can install through your package manager. Once those are installed, you can install rbenv and use it to install Ruby.

      First, update your package list:

      Next, install the dependencies required to install Ruby:

      • sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libreadline-dev libncurses5-dev libffi-dev libgdbm-dev

      After installing the dependencies, you can install rbenv itself. Use curl to transfer information from the rbenv repository on GitHub into the directory ~/.rbenv:

      • curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash

      Next, add ~/.rbenv/bin to your $PATH so that you can use the rbenv command line utility. Do this by altering your ~/.bashrc file so that it affects future login sessions:

      • echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

      Then, add the command eval "$(rbenv init -)" to your ~/.bashrc file so rbenv loads automatically:

      • echo 'eval "$(rbenv init -)"' >> ~/.bashrc

      Next, apply the changes you made to your ~/.bashrc file to your current shell session:

      Verify that rbenv is set up properly by running the type command, which will display more information about the rbenv command:

      Your terminal window will display the following:

      Output

      rbenv is a function rbenv () { local command; command="${1:-}"; if [ "$#" -gt 0 ]; then shift; fi; case "$command" in rehash | shell) eval "$(rbenv "sh-$command" "$@")" ;; *) command rbenv "$command" "$@" ;; esac }

      Next, install the ruby-build plugin. This plugin adds the rbenv install command, which makes the installation process of new versions of Ruby less complex. To install ruby-build, first clone the ruby-build GitHub repository:

      • git clone https://github.com/rbenv/ruby-build.git

      After running this command, you’ll have a directory named ruby-build in your working directory. Within the ruby-build directory is a script named install.sh which you’ll use to actually install ruby-build.

      Before running this script, take a moment to review its contents. Rather than opening the file with a text editor, you can print its contents to your terminal’s output with the following command:

      • cat ruby-build/install.sh

      Output

      #!/bin/sh # Usage: PREFIX=/usr/local ./install.sh # # Installs ruby-build under $PREFIX. set -e cd "$(dirname "$0")" if [ -z "${PREFIX}" ]; then PREFIX="/usr/local" fi BIN_PATH="${PREFIX}/bin" SHARE_PATH="${PREFIX}/share/ruby-build" mkdir -p "$BIN_PATH" "$SHARE_PATH" install -p bin/* "$BIN_PATH" install -p -m 0644 share/ruby-build/* "$SHARE_PATH"

      Notice the second line of this file that reads # Usage: PREFIX=/usr/local ./install.sh. This commented-out line explains that in order to execute this script and install ruby-build, you must precede the script with PREFIX=/usr/local. This will create a temporary environment variable that will affect how the script is run. Essentially, this will cause the string $PREFIX to be replaced with /usr/local any time it appears in the script and will ultimately cause all the necessary ruby-build files to be installed within the /usr/local directory. This environment variable is only temporary and will cease to exist once the script terminates.

      Create this temporary environment variable and run the script with the following command. Note that this command includes sudo before calling the script. This is necessary since you must have advanced privileges to install files to the /usr/local directory:

      • PREFIX=/usr/local sudo ./ruby-build/install.sh

      At this point, you have both rbenv and ruby-build installed. Let’s install Ruby next.

      Step 2 – Installing Ruby with ruby-build

      With the ruby-build plugin now installed, you can install whatever versions of Ruby that you may need with a single command. First, list all the available versions of Ruby:

      The output of that command will be a list of versions that you can choose to install:

      Output

      2.6.8 2.7.4 3.0.2 jruby-9.2.19.0 mruby-3.0.0 rbx-5.0 truffleruby-21.2.0.1 truffleruby+graalvm-21.2.0 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions.

      Now let’s install Ruby 3.0.2:

      Installing Ruby can be a lengthy process, so be prepared for the installation to take some time to complete.

      Once it’s done installing, set it as your default version of Ruby with the global sub-command:

      Verify that Ruby was properly installed by checking its version number:

      If you installed version 3.0.2 of Ruby, this command will return output like this:

      Output

      ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

      To install and use a different version of Ruby, run the rbenv commands with a different version number, as in rbenv install 2.3.0 followed by rbenv global 2.3.0.

      You now have at least one version of Ruby installed and have set your default Ruby version. Next, you will set up gems and Rails.

      Step 3 – Working with Gems

      Gems are the way Ruby libraries are distributed. You use the gem command to manage these gems, and use this command to install Rails.

      When you install a gem, the installation process generates local documentation. This can add a significant amount of time to each gem’s installation process, so turn off local documentation generation by creating a file called ~/.gemrc which contains a configuration setting to turn off this feature:

      • echo "gem: --no-document" > ~/.gemrc

      Bundler is a tool that manages gem dependencies for projects. Install the Bundler gem next, as Rails depends on it:

      You’ll receive the following output:

      Output

      Fetching bundler-2.2.27.gem Successfully installed bundler-2.2.27 1 gem installed

      You can use the gem env command (the subcommand env is short for environment) to learn more about the environment and configuration of gems. You can confirm where gems are being installed by using the home argument, like this:

      You’ll receive an output similar to this:

      Output

      /home/sammy/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0

      Once you have gems set up, you can install Rails.

      Step 4 – Installing Rails

      To install Rails, use the gem install command along with the -v flag to specify the version. For this tutorial, you’ll use version 6.1.4.1:

      • gem install rails -v 6.1.4.1

      The gem command installs the gem you specify, as well as any of its dependencies. Rails is a complex web development framework and has many dependencies, so the process will take some time to complete. Eventually, you’ll receive a message stating that Rails is installed, along with its dependencies:

      Output

      ... Successfully installed rails-6.1.4.1 37 gems installed

      Note: If you would like to install a different version of Rails, you can list the valid versions of Rails by doing a search, which will output a list of possible versions. You can then install a specific version, such as 4.2.7:

      • gem search '^rails$' --all
      • gem install rails -v 4.2.7

      If you would like to install the latest version of Rails, run the command without a version specified:

      rbenv works by creating a directory of shims, which point to the files used by the Ruby version that’s currently enabled. Through the rehash sub-command, rbenv maintains shims in that directory to match every Ruby command across every installed version of Ruby on your server. Whenever you install a new version of Ruby or a gem that provides commands as Rails does, you should run the following:

      Verify that Rails has been installed properly by printing its version, with the following command:

      If it’s installed properly, this command will return the version of Rails that was installed:

      Output

      Rails 6.1.4.1

      At this point, you can begin testing your Ruby on Rails installation and start to develop web applications. Now let’s review how to keep the rbenv up-to-date.

      Step 5 – Updating rbenv

      Since you installed rbenv manually using Git, you can upgrade your installation to the most recent version at any time by using the git pull command in the ~/.rbenv directory:

      This will ensure that you are using the most up-to-date version of rbenv available.

      Step 6 – Uninstalling Ruby versions

      As you download additional versions of Ruby, you may accumulate more versions than you would like in your ~/.rbenv/versions directory. Use the ruby-build plugin’s uninstall subcommand to remove these previous versions.

      The following command will uninstall Ruby version 3.0.2:

      With the rbenv uninstall command you can clean up old versions of Ruby so that you do not have more installed than you are currently using.

      Step 7 – Uninstalling rbenv

      If you’ve decided you no longer want to use rbenv, you can remove it from your system.

      To do this, first open your ~/.bashrc file in your editor. In this example, we will use nano:

      Find and remove the following two lines from the file:

      ~/.bashrc

      ...
      export PATH="$HOME/.rbenv/bin:$PATH"
      eval "$(rbenv init -)"
      

      After removing these lines, save the file and exit the editor. If you used nano, you can exit by pressing CTRL + X then Y and ENTER.

      Then remove rbenv and all installed Ruby versions with the following command:

      Log out and back in to apply the changes to your shell.

      Conclusion

      In this tutorial, you installed rbenv and gem to install the entire Ruby on Rails framework. From here, you can begin creating your web development application projects. If you want to learn more about making those environments more robust you can check out our series on How To Code In Ruby.



      Source link

      How To Build a Python REST API with Fauna and Deploy it to DigitalOcean App Platform


      Introduction

      Many developers don’t have the time or experience to set up and manage infrastructure for their applications. To keep up with deadlines and reduce costs, developers need to find solutions that allow them to deploy their apps to the cloud as quickly and efficiently as possible to focus on writing the code and delivering new features to their customers. Together, DigitalOcean’s App Platform and Fauna provide that ability.

      DigitalOcean App Platform is a Platform-as-a-Service (PaaS) that abstracts the infrastructure that runs your apps. It also lets you deploy applications by pushing your code to a Git branch.

      Fauna is a powerful data layer for applications of any size. As you’ll see in this tutorial, with Fauna, you can get a database up and running quickly without having to worry about the database operations.

      Together, these two solutions let you focus on your application instead of managing your infrastructure.

      In this tutorial, you’ll integrate Fauna with Python by writing a minimal REST API using the Flask framework. You’ll then deploy the API to DigitalOcean’s App Platform from a Git repository.The API will consist of:

      • A public /signup POST endpoint for creating users in the Users collection.
      • A public /login POST endpoint for authenticating with the documents in the Users collection.
      • A private /things GET endpoint for fetching a list of Fauna documents from the Things collection.

      The finished Python project is available at this Github repository.

      Prerequisites

      Before starting this tutorial, you will need:

      Step 1 — Setting Up the Fauna Database

      In the first step, you will configure a Fauna database and create the collections for the API. Fauna is a document-based database rather than a traditional table-based relational database. Fauna stores your data in documents and collections, which are groups of documents.

      To create the collections, you will execute queries using FQL, Fauna’s native query language. FQL is an expressive and powerful query language that gives you access to the full power of Fauna.

      To get started, log into Fauna’s dashboard. After logging in, click on the Create Database button at the top.

      In the New Database form, use PYTHON_API for the database name:

      Creating a Fauna database

      Leave Pre-populate with demo data unchecked. Press the Save button.

      After creating the database, you will see the home section for your database:

      Fauna database home

      You’re now going to create two collections:

      • The Users collection that will store documents with authentication information.
      • The Things collection to store some mock data to test your API.

      To create these collections, you’ll execute some FQL queries in the dashboard’s shell. Access the shell from the main dashboard menu on the left:

      Fauna dashboard shell

      Write the following FQL query in the bottom panel of the shell to create a collection called Things by using the CreateCollection function:

      CreateCollection({name: "Things"})
      

      Press the RUN QUERY button. You will get a result similar to this in the shell’s top panel:

      {
        ref: Collection("Things"),
        ts: 1614805457170000,
        history_days: 30,
        name: "Things"
      }
      

      The result shows four fields:

      • ref is a reference to the collection itself.
      • ts is the timestamp of its creation in microseconds.
      • history_days is how long Fauna will retain changes on documents’ changes.
      • name is the collection name.

      Next, create the Users collection with the following query:

      CreateCollection({name: "Users"})
      

      Now that both collections are in place, you will create your first document.

      Documents in Fauna are somewhat similar to JSON objects. Documents can store strings, numbers, and arrays, but they can also use Fauna data types. A common Fauna type is Ref, which represents a reference to a document in a collection.

      The Create function creates a new document into the specified collection. Run the following query to create a document in the Things collection with two fields:

      Create(
        Collection("Things"),
        {
          data: {
            name: "Banana",
            color: "Yellow"
          }
        }
      )
      

      After running that query, Fauna will return the created document:

      {
        ref: Ref(Collection("Things"), "292079274901373446"),
        ts: 1614807352895000,
        data: {
          name: "Banana",
          color: "Yellow"
        }
      }
      

      The result shows the following fields:

      • ref of type Ref is a reference to this document in the Things collection with the ID 292079274901373446. Do note that your document will have a different ID.
      • ts is the timestamp of its creation in microseconds.
      • data is the actual content of the document.

      This result looks similar to the result you got when you created a collection. That’s because all entities in Fauna (collections, indexes, roles, etc) are actually stored as documents.

      To read documents, use the Get function which accepts a reference of a document. Run the Get query using the reference for your document:

      Get(Ref(Collection("Things"), "292079274901373446"))
      

      The result is the full document:

      {
        ref: Ref(Collection("Things"), "292079274901373446"),
        ts: 1614807352895000,
        data: {
          name: "Banana",
          color: "Yellow"
        }
      }
      

      To get all references for documents stored in a collection, use the Documents function with the Paginate function:

      Paginate(Documents(Collection("Things")))
      

      This query returns a page with an array of references:

      {
        data: [Ref(Collection("Things"), "292079274901373446")]
      }
      

      To get actual documents instead of references, iterate over the references using Map. Then use a Lambda (an anonymous function) to iterate over the array of references and Get each reference:

      Map(
        Paginate(Documents(Collection("Things"))),
        Lambda("ref", Get(Var("ref")))
      )
      

      The result is an array containing full documents:

      {
        data: [
          {
            ref: Ref(Collection("Things"), "292079274901373446"),
            ts: 1614807352895000,
            data: {
              name: "Banana",
              color: "Yellow"
            }
          }
        ]
      }
      

      You’re now going to create the Users_by_username index. You typically use indexes in Fauna to catalog, filter, and sort data, but you can also use them for other purposes like enforcing unique constraints.

      The Users_by_username index will find users by their username, and also enforce a unique constraint to prevent two documents from having the same username.

      Execute this code in the shell to create the index:

      CreateIndex({
        name: "Users_by_username",
        source: Collection("Users"),
        terms: [{ field: ["data", "username"] }],
        unique: true
      })
      

      The CreateIndex function will create an index with the configured settings:

      • name is the name of the index.
      • source is the collection (or collections) the index will index data from.
      • terms is the search/filter terms you’ll pass to this index when using it to find documents.
      • unique means that the indexed values will be unique. In this example, the username property of the documents in the Users collection will be enforced as unique.

      To test the index, create a new document inside the Users collection by running the following code in the Fauna shell:

      Create(
        Collection("Users"),
        {
          data: {
            username: "sammy"
          }
        }
      )
      

      You’ll see a result like the following:

      {
        ref: Ref(Collection("Users"), "292085174927098368"),
        ts: 1614812979580000,
        data: {
          username: "sammy"
        }
      }
      

      Now try to create a document with the same username value:

      Create(
        Collection("Users"),
        {
          data: {
            username: "sammy"
          }
        }
      )
      

      You’ll receive an error now:

      Error: [
        {
          "position": [
            "create"
          ],
          "code": "instance not unique",
          "description": "document is not unique."
        }
      ]
      

      Now that the index is in place, you can query it and fetch a single document. Run this code in the shell to fetch the sammy user using the index:

      Get(
        Match(
          Index("Users_by_username"),
          "sammy"
        )
      )
      

      Here’s how it works:

      • Index returns a reference to the Users_by_username index.
      • Match returns a reference to the matched document (the one that has a username with the value of sammy).
      • Get takes the reference returned by Match, and fetches the actual document.

      The result of this query will be:

      {
        ref: Ref(Collection("Users"), "292085174927098368"),
        ts: 1614812979580000,
        data: {
          username: "sammy"
        }
      }
      

      Delete this testing document by passing its reference to the Delete function:

      Delete(Ref(Collection("Users"), "292085174927098368"))
      

      Next you’ll configure security settings for Fauna so you can connect to it from your code.

      Step 2 — Configuring a Server Key and Authorization Rules

      In this step you’ll create a server key that your Python application will use to communicate with Fauna. Then you’ll configure access permissions.

      To create a key, go to the Security section of the Fauna dashboard by using the main menu on the left. Once there:

      1. Press the New Key button.
      2. Select the Server role.
      3. Press Save.

      Creating a Fauna key

      After saving, the dashboard will show you the key’s secret. Save the secret somewhere safe and never commit it to your Git repository.

      Warning: The Server role is omnipotent and anyone with this secret would have full access to your database. As its name implies, this is the role typically used by trusted server applications, although it is also possible to create a key with a custom role with limited privileges. When you create production applications, you’ll want to make a more restrictive role.

      By default, everything in Fauna is private, so you’re now going to create a new role to allow the logged-in users to read documents from the Things collection.

      In the Security section of the dashboard, go to Roles, and create a new custom role with the name User.

      In the Collections dropdown, add the Things collection and press the Read permission so that it shows a green check mark:

      Configuring the permissions of a Fauna role

      Before saving the role, go to the Membership tab and add the Users collection to the role:

      Configuring the memerbship of a Fauna role

      You can now save your User custom role by pressingthe Save button.

      Now any logged-in user from a document in the Users collection will be able to read any document from the Things collection.

      With authentication and authorization in place, let’s now create the Python API that will talk to Fauna.

      Step 3 — Building the Python Application

      In this step you will build a small REST API using the Flask framework, and you’ll write FQL queries in Python, connecting to your Fauna database using the the Fauna driver.

      To get started, create a project folder and access it from your terminal.

      First install Flask with:

      Then install the Fauna Python driver with:

      In your project folder, create the file main.py and add the following code to the file, which adds the necessary imports, the FAUNA_SECRET environment variable, and the basic configuration of the Flask application:

      main.py

      import os
      FAUNA_SECRET = os.environ.get('FAUNA_SECRET')
      
      import flask
      from flask import request
      
      import faunadb
      from faunadb import query as q
      from faunadb.client import FaunaClient
      
      app = flask.Flask(__name__)
      app.config["DEBUG"] = True
      

      The FAUNA_SECRET environment variable will carry the server secret you created earlier. To be able to run this application, locally or in the cloud, this variable needs to be injected. Without it, the application won’t be able to connect to Fauna. You’ll provide this environment variable when you launch the app.

      Now add the the /signup route to the main.py file. This will create new documents in the Users collection:

      main.py

      @app.route('/signup', methods=['POST'])
      def signup():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.create(
                      q.collection("Users"),
                      {
                          "data": {
                              "username": body["username"]
                          },
                          "credentials": {
                              "password": body["password"]
                          }
                      }
                  )
              )
      
              return {
                  "userId": result['ref'].id()
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 409
      

      Note that the Fauna client is being instantiated on every request using the server secret:

      main.py

      ...
      client = FaunaClient(secret=FAUNA_SECRET)
      ...
      

      Once users are logged in, the API will execute queries on behalf of each user using different secrets, which is why it makes sense to instantiate the client on every request.

      Unlike other databases, the Fauna client does not maintain a persistent connection. From the outside world, Fauna behaves like an API; every query is a single HTTP request.

      After the client is ready, the FQL query executes, which creates a new document in the Users collection. Each Fauna driver translates idiomatic syntax to FQL statements. In this route, you added this query:

      main.py

      ...
      q.create(
          q.collection("Users"),
          {
              "data": {
                  "user": json["user"]
              },
              "credentials": {
                  "password": json["password"]
              }
          }
      )
      ...
      

      This is what this query would look like in native FQL:

      Create(
          Collection("Users"),
          {
              "data": {
                  "user": "sammy"
              },
              "credentials": {
                  "password": "secretpassword"
              }
          }
      )
      

      In addition to the document data, you’re adding a credentials configuration with the user’s password. This part of the document is completely private. You will never be able to read a document’s credentials afterwards. When using Fauna’s authentication system, it’s not possible to expose users’ passwords by mistake.

      Finally, if there’s already a user with the same username, a faunadb.errors.BadRequest exception will be raised, and a 409 response with the error information will be returned to the client.

      Next, add the /login route in the main.py file to authenticate the user and password. This follows a similar pattern as the previous example; you execute a query using the Fauna connection and if the authentication fails, you raise a faunadb.errors.BadRequest exception and return a a 401 response with the error information. Add this code to main.py:

      main.py

      @app.route('/login', methods=['POST'])
      def login():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.login(
                      q.match(
                          q.index("Users_by_username"),
                          body["username"]
                      ),
                      {"password": body["password"]}
                  )
              )
      
              return {
                  "secret": result['secret']
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      This is the FQL query used to authenticate users with Fauna:

      main.py

      q.login(
          q.match(
              q.index("Users_by_username"),
              body["username"]
          ),
          {"password": body["password"]}
      )
      

      This is what this query would look like in native FQL:

      Login(
          Match(
              Index("Users_by_username"),
              "sammy"
          ),
          {"password": "secretpassword"}
      )
      

      Match returns a reference to a document using the Users_by_username index that we created previously.

      If the provided password matches the referenced document, Login will create a new token and return a dictionary with the following keys:

      • ref with a reference to the token for the new document.
      • ts with the timestamp of the transaction.
      • instance with a reference to the document that was used to do the authentication.
      • secret with the token’s secret that will be used to make further queries to Fauna.

      If you run that FQL query into your Fauna dashboard’s shell you will see something similar to this:

      {
        ref: Ref(Ref("tokens"), "292001047221633538"),
        ts: 1614732749110000,
        instance: Ref(Collection("Users"), "291901454585692675"),
        secret: "fnEEDWVnxbACAgQNBIxMIAIIKq1E5xvPPdGwQ_zUFH4F5Dl0neg"
      }
      

      Depending on the security requirements of the project, you have to decide how to handle the token’s secret. If this API was meant to be consumed by browsers, you might return the secret inside a secure cookie or an encrypted JSON Web Token (JWT). Or you might store it as session data somewhere else, like a Redis instance. For the purpose of this demo, you return it in the body of the HTTP response:

      Finally, add this bit of code to main.py, which will start the Flask application:

      main.py

      app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)))
      

      It’s important to specify the 0.0.0.0 address. Once deployed to the cloud, this application will run in a Docker container. It won’t be able to receive requests from remote clients if it is running on 127.0.0.1, which is the default address for Flask applications.

      This is the complete main.py file so far:

      main.py

      import os
      FAUNA_SECRET = os.environ.get('FAUNA_SECRET')
      
      import flask
      from flask import request
      
      import faunadb
      from faunadb import query as q
      from faunadb.client import FaunaClient
      
      app = flask.Flask(__name__)
      app.config["DEBUG"] = True
      
      @app.route('/signup', methods=['POST'])
      def signup():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.create(
                      q.collection("Users"),
                      {
                          "data": {
                              "username": body["username"]
                          },
                          "credentials": {
                              "password": body["password"]
                          }
                      }
                  )
              )
      
              return {
                  "userId": result['ref'].id()
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 409
      
      @app.route('/login', methods=['POST'])
      def login():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.login(
                      q.match(
                          q.index("Users_by_username"),
                          body["username"]
                      ),
                      {"password": body["password"]}
                  )
              )
      
              return {
                  "secret": result['secret']
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      
      app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)))
      

      Save the file.

      To launch this server locally from your terminal, use the following command with the FAUNA_SECRET environment variable with the secret you obtained when creating the server key:

      • FAUNA_SECRET=your_fauna_server_secret python main.py

      After triggering that command, Flask will show a warning informing you it is running with a development WSGI server. This is fine for the purpose of this demo so you can safely ignore this warning.

      Test your API by making HTTP requests using the curl command. Open a new terminal window and run the following command:

      Create a user with the following command:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/signup

      You’ll see the following response, indicating a successful user creation:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 37
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:00:47 GMT
      
      {
        "userId": "292092166117786112"
      }
      

      Now authenticate that user with this command:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/login

      You’ll get this successful response:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnEEDbhO3jACAAQNBIxMIAIIOlDxujk-VJShnnhkZkCUPKIHxbc"
      }
      

      Close the terminal window where you ran your curl commands and switch back to the terminal where your Python server is running. Stop your server by pressing CTRL+C.

      Now that the application is working, we’re going to add a private endpoint that requires users to be authenticated.

      Step 4 — Adding a Private Endpoint

      In this step, you’ll add a private endpoint to the API, which will require the user to be authenticated first.

      First, create a new route in the main.py file. This route will respond to the /things endpoint. Place it above the line that starts the server with the app.run() method:

      main.py

      @app.route('/things', methods=['GET'])
      def things():
      

      Next, in the /things route, instantiate the Fauna client:

      main.py

          userSecret = request.headers.get('fauna-user-secret')
          client = FaunaClient(secret=userSecret)
      

      Instead of using the server secret, this route is using the user’s secret from the fauna-user-secret HTTP header which is used to instantiate the Fauna client. By using the users’ secrets instead of the server secret, FQL queries will now be subject to the authorization rules we’ve configured previously in the dashboard.

      Then add this try block to the route to execute the query:

      main.py

          try:
              result = client.query(
                  q.map_(
                      q.lambda_("ref", q.get(q.var("ref"))),
                      q.paginate(q.documents(q.collection("Things")))
                  )
              )
      
              things = map(
                  lambda doc: {
                      "id": doc["ref"].id(),
                      "name": doc["data"]["name"],
                      "color": doc["data"]["color"]
                  },
                  result["data"]
              )
      
              return {
                  "things": list(things)
              }
      

      This executes an FQL query and parses the Fauna response into a serializable type that is then returned as a JSON string in the body of the HTTP response.

      Finally, add this except block to the route:

      main.py

          except faunadb.errors.Unauthorized as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      If the request doesn’t contain a valid secret, a faunadb.errors.Unauthorized exception will be raised and a 401 response with the error information will be returned.

      This is the full code for the /things route:

      main.py

      @app.route('/things', methods=['GET'])
      def things():
      
          userSecret = request.headers.get('fauna-user-secret')
          client = FaunaClient(secret=userSecret)
      
          try:
              result = client.query(
                  q.map_(
                      q.lambda_("ref", q.get(q.var("ref"))),
                      q.paginate(q.documents(q.collection("Things")))
                  )
              )
      
              things = map(
                  lambda doc: {
                      "id": doc["ref"].id(),
                      "name": doc["data"]["name"],
                      "color": doc["data"]["color"]
                  },
                  result["data"]
              )
      
              return {
                  "things": list(things)
              }
      
          except faunadb.errors.Unauthorized as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      Save the file and run your server again:

      • FAUNA_SECRET=your_fauna_server_secret python main.py

      To test this endpoint, first obtain a secret by authenticating with valid credentials. Open a new terminal window and execute this curl command:

      • curl -i -d '{"username":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/login

      This command returns a succesful response, although the value for secret will be different:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnEEDb...."
      }
      

      Now hen do a GET request to /things using the secret:

      curl -i -H 'fauna-user-secret: fnEEDb...' -X GET http://0.0.0.0:8080/things
      

      You’ll get another successful response:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 118
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:14:49 GMT
      
      {
        "things": [
          {
            "color": "Yellow",
            "id": "292079274901373446",
            "name": "Banana"
          }
        ]
      }
      

      Close your terminal window where you ran the curl commands. Return to your window where your server is running and stop the server with CTRL+C.

      Now that you have a working app, you’re ready to deploy it.

      Step 4 — Deploying to DigitalOcean

      In the final step of this tutorial, you will create an app on App Platform and deploy it from a GitHub repository.

      Before pushing the project to a Git repository, be sure to run the following command in the project’s folder:

      • pip freeze > requirements.txt

      This will create a requirements.txt file with the list of dependencies that need to be installed once the application is deployed.

      Now initialize your project directory as a Git repository:

      Now execute the following command to add files to your repository:

      This adds all the files in the current directory.

      With the files added, make your initial commit:

      • git commit -m "Initial version of the site"

      Your files will commit.

      Open your browser and navigate to GitHub, log in with your profile, and create a new repository called sharkopedia. Create an empty repository without a README or license file.

      Once you’ve created the repository, return to the command line to push your local files to GitHub.

      First, add GitHub as a remote repository:

      • git remote add origin https://github.com/your_username/sharkopedia

      Next, rename the default branch main, to match what GitHub expects:

      Finally, push your main branch to GitHub’s main branch:

      Your files will transfer. You’re now ready to deploy your app.

      Note: To be able to create an app on App Platform, you’ll first need to add a payment method to your DigitalOcean account.

      The application will run on a container which costs $5 per month, although only a few cents will be needed to test it out. Don’t forget to delete the application once you’re done or you’ll continue to be charged.

      Go to the Apps section of the DigitalOcean dashboard, and click on Launch Your App:

      Select the source for deployment. You will need to authorize DigitalOcean to read your Github repositories. Once you’ve authorized access, select the repository with your Python project and the branch that contains the version of the app you want to deploy:

      Selecting a repository and branch

      At this point, App Platform will determine that your project uses Python and will let you configure some application options:

      Configuring the app options

      Set the following options

      • Ensure the Type is Web Service.
      • Create aFAUNA_SECRET environment variable with your server secret.
      • Set the Run Command to python main.py.
      • Set the HTTP Port to 8080.

      Next, enter a name for your app and select a deploy region:

      Configuring the name of the app and deploy region

      Next, choose the Basic plan and Basic Size that costs $5 per month:

      Selecting the app plan

      After that, scroll down and click on Launch Your App.

      Once you’ve finished configuring the app, a container will be created and deployed with your application. This first-time initialization will take a couple of minutes, but subsequent deploys will be much faster.

      In the app’s dashboard you’ll see a green check mark to indicate the deploy process has finished successfully:

      App is running

      You will now be able to execute HTTP requests to the provided app domain. Execute the following command in your terminal, substituting your_app_name with your actual app name, to return a new secret for the sammy user:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST https://your_app_name.ondigitalocean.app/login

      You’ll receive a response similar to the following:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnAADbhO3jACEEQNBIxMIAOOIlDxujk-VJShnnhkZkCUPKIskdjfh"
      }
      

      Your application is now up and running on Digital Ocean.

      Conclusion

      In this tutorial you created a Python REST API using Fauna as the data layer, and you deployed it to DigitalOcean App Platform.

      To keep learning about Fauna and dive deeper into FQL, check out the Fauna Documentation.



      Source link