One place for hosting & domains

      Create

      How to Create and Deploy Your First Eleventy Website


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

      Eleventy (also known as 11ty) is a static site generator (SSG) for building websites. It was launched in 2017 by Zach Leatherman as a JavaScript-based alternative to Jekyll, one of the first mainstream SSGs, which is written in Ruby. Eleventy has gained a reputation as one of the most flexible and performant options for building static websites, leading to steadily rising adoption rates in the Jamstack ecosystem.

      It’s important to note that Eleventy is not a JavaScript framework, and it does not include any client-side JavaScript. It takes template files specified in HTML, Markdown, or your choice of templating language, and outputs a complete, static website ready to be deployed to a web server of your choice.

      While most other SSGs are restricted to just one templating language, Eleventy supports multiple templating languages, such as HTML, Liquid, Markdown, Nunjucks, Handlebars, moustache, EJS, Haml, Pug, etc., and you can even combine them in the same project. This flexibility is one of the things that makes Eleventy stand out from its competition.

      In this tutorial, you’ll develop a static website from scratch with Eleventy and deploy it to DigitalOcean’s App Platform for free.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Setting Up the Project

      Unlike competitors such as Jekyll and Hugo, Eleventy does not provide a way to scaffold a new project, so you’ll need to create a regular Node.js project, and then add Eleventy as a dependency.

      The first step is to launch the terminal on your computer, create a new directory somewhere on your filesystem, and change into it as shown below.

      • mkdir eleventy-blog
      • cd eleventy-blog

      At the root of the eleventy-blog directory, initialize the project with a package.json file with npm init -y, and install Eleventy as a development dependency by passing the -D flag to the install subcommand.

      • npm init -y
      • npm install -D @11ty/eleventy

      Once the Eleventy package is installed, inspect the contents of your project directory with ls. It will contain a package.json file, a package-lock.json file, and a node_modules directory.

      The output should look similar to this:

      Output

      node_modules package-lock.json package.json

      Open the package.json file in your favorite text editor, then replace the existing scripts property with the highlighted lines below.

      eleventy-blog/package.json

      {
        "name": "eleventy-blog",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "build": "eleventy",
          "start": "eleventy --serve"
        },
        "keywords": [],
        "author": "",
        "license": "ISC",
        "devDependencies": {
          "@11ty/eleventy": "^0.12.1"
        }
      }
      

      There are two scripts here: build for building the website files, and start for running the Eleventy web server on http://localhost:8080.

      Save the file, and then run the command below to build the website:

      The output should look similar to this:

      Output

      > eleventy-blog@1.0.0 build > eleventy Wrote 0 files in 0.04 seconds (v0.12.1)

      The output indicates that you haven’t created any files yet, so there is nothing to build. In the next step, you’ll begin adding the necessary files that are needed for the website.

      The final directory structure of the project you’ll be working on is shown below. You’ll start from an empty directory, and incrementally add new features until you arrive at this structure.

      .
      ├── about
      │   └── index.md
      ├── css
      │   └── style.css
      ├── _data
      │   └── site.json
      ├── _includes
      │   ├── layouts
      │   │   ├── base.njk
      │   │   ├── page.njk
      │   │   └── post.njk
      │   └── nav.njk
      ├── index.njk
      ├── node_modules
      ├── package.json
      ├── package-lock.json
      ├── posts
      │   ├── first-post.md
      │   ├── second-post.md
      │   └── third-post.md
      └── _site
          ├── about
          │   └── index.html
          ├── css
          │   └── style.css
          ├── index.html
          └── posts
              ├── first-post
              │   └── index.html
              ├── second-post
              │   └── index.html
              └── third-post
                  └── index.html
      

      In this step, you created a Node.js project and added Eleventy as a dependency. In the next step, you’ll choose a templating language.

      Step 2 — Choosing a Templating Language

      For the purpose of this tutorial, we’ll use the Nunjucks template, a common choice for many Eleventy projects. (Depending on your preference, you could also choose a different templating language.)

      In the root of your project directory, create an index.njk file and open it in your text editor. Add a “Hello world” message to the file as shown below, then save the file.

      eleventy-blog/index.njk

      <h1>Hello, world!</h1>
      

      Once saved, run the build command again. It will convert the index.njk file to an index.html file and output it into a new _site directory at the root of the project.

      The output should look similar to this:

      Output

      > eleventy-blog@1.0.0 build > eleventy Writing _site/index.html from ./index.njk. Wrote 1 file in 0.08 seconds (v0.12.1)

      At this point, you can view the website in the browser by starting a development server at http://localhost:8080 as shown below.

      The output should look similar to this:

      Output

      > eleventy-blog@1.0.0 start > eleventy --serve Writing _site/index.html from ./index.njk. Wrote 1 file in 0.08 seconds (v0.12.1) Watching.. [Browsersync] Access URLs: ----------------------------------- Local: http://localhost:8080 External: http://172.29.217.37:8080 ----------------------------------- UI: http://localhost:3001 UI External: http://localhost:3001 ----------------------------------- [Browsersync] Serving files from: _site

      If you wish to use a different port, you can specify it through the --port option, as shown here:

      • npm start -- --port 4040 to set a different port

      The -- separator in the command above is used to distinguish the parameters passed to npm command itself from those passed to the script. After starting the development server, open http://localhost:8080 in your web browser to see the site in action. You can exit the server at any time by pressing Ctrl + C on your keyboard.

      Eleventy hello world in Microsoft Edge

      In this step, you used Nunjucks as the templating language and began building a website. In the next section, you’ll learn about layouts in Eleventy and create a homepage for the website.

      Step 3 — Creating the Homepage

      In order to make your project more flexible and scalable from the start, you’ll need to create a base template that will be applied to all the pages of the site. Conventionally, this is called the base layout, and it needs to go into a layouts directory nested within an _includes directory. Create the _includes/layouts directory using the command below:

      • mkdir -p _includes/layouts

      In the _includes/layouts directory, create a base.njk file and open it in your text editor. Copy and paste the following code into the file. This is basic HTML5 boilerplate that will serve as the foundation for all the pages on the website.

      _includes/layouts/base.njk

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{{ title }}</title>
      </head>
      <body>
        <header>
          <h1>{{ title }}</h1>
        </header>
        <main>
          {{ content | safe }}
        </main>
      </body>
      </html>
      

      The contents in double curly braces are Nunjucks variables that will be replaced accordingly when a derivative page is being built. The {{ title }} variable will be supplied through the page’s front matter block while the {{ content }} will be replaced with all incoming page content that is not part of the front matter. The latter is piped through the safe filter to prevent it from being escaped.

      Return to the index.njk file in your project root, and modify it as shown below:

      eleventy-blog/index.njk

      ---
      title: Homepage
      layout: layouts/base.njk
      ---
      
      <h1>Welcome to this brand new Eleventy website!</h1>
      

      The contents on either side of the triple dashes constitute the front matter of the file, while the rest of the file is what will be passed to your layouts as its content. In the front matter, the title and layout of the file are specified accordingly.

      If your development server is still running, head over to your site’s localhost URL to view the changes, or start the server first with npm start before attempting to view it in a web browser.

      Eleventy homepage in action

      As you can see from the above screenshot, the base.njk template has taken effect on the homepage.

      In this step, you added a base template for the pages of your site, and created a homepage. However, it doesn’t yet have any styling beyond the browser defaults. In the next section, you’ll improve the design of the website by adding a navigation menu.

      The _includes directory is where you’ll place the different components of the website. The contents of this directory are partial files that can be placed in your layout files to facilitate reuse. In this section, you’ll create the navigation menu as a partial, and include it in the base layout.

      In the _includes directory, create a new file called nav.njk. Open it in your editor and populate it with the code below. It is the markup for the top navigation bar, and it includes the title of the site as well as links to the homepage and a yet-to-be created “About” page.

      eleventy-blog/_includes/nav.njk

      <nav class="navbar is-light" role="navigation" aria-label="main navigation">
        <div class="navbar-start">
          <div class="navbar-item has-text-weight-bold">
            My Eleventy Blog
          </div>
        </div>
        <div class="navbar-end">
          <a href="https://www.digitalocean.com/" class="navbar-item">
            Home
          </a>
      
          <a href="http://www.digitalocean.com/about" class="navbar-item">
            About Me
          </a>
        </div>
      </nav>
      

      Save and close the nav.njk file, and open the base template file (_includes/layouts/base.njk) in your editor. Go ahead and include the new navigation partial in this file through the include syntax shown below:

      eleventy-blog/_includes/layouts/base.njk

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>{{ title }}</title>
        </head>
        <body>
          <header>{% include "nav.njk" %}</header>
          <main>
            {{ content | safe }}
          </main>
        </body>
      </html>
      

      In the browser, the site should look like this:

      Navigation

      When you check the home page once again, the navigation menu should show up just like in the screenshot above. The title of the site “My Eleventy Blog” is hardcoded into the nav partial, but this is suboptimal because it’s likely that you will repeat the title elsewhere on the site, and changing it later becomes tedious since you’ll then have to find each place it was used.

      A better approach is to supply this value through a global data file. These are JSON files placed in a _data directory at the project root that provide global data accessible to all template files. At the project root, create a _data directory, followed by a site.json file within it. Open site.json in your text editor and specify the site’s title using the code below.

      eleventy-blog/_data/site.json

      {
        "title": "My Eleventy Blog",
        "url": "https://example.com/",
        "language": "en-US",
        "description": "A simple blog with awesome content"
      }
      

      At this point, you can save and close the file, then return to the nav.njk file in the _includes directory and replace the hardcoded site title with the appropriate variable.

      eleventy-blog/_includes/nav.njk

      . . .
      <div class="navbar-item has-text-weight-bold">
        {{ site.title }}
      </div>
      . . .
      

      The site should look exactly the same as before, but this small change makes setting and updating global data much easier. One thing to note about global variables is that they are scoped to the name of the JSON file, which is why we used {{ site.title }} above. You can create other data files as needed, and use them in your templates. For example, you can have an author.json file that contains your personal details such as your name, bio, and links to your social media profiles. Such data could then be accessed through variables (such as {{ author.bio }}) on any page of the website.

      Return to your index.njk file at the project root and update its contents as shown below so that it uses the site title and description:

      eleventy-blog/index.njk

      ---
      title: Homepage
      layout: layouts/base.njk
      ---
      
      <section class="hero is-medium is-primary is-bold">
        <div class="hero-body">
          <div class="container">
            <h1 class="title">{{ site.title }}</h1>
            <h2 class="subtitle">{{ site.description }}</h2>
          </div>
        </div>
      </section>
      

      In the browser, the site should look like this:

      Updated homepage

      In this step, you added a navigation menu to the website. However, the site is using default styling. In the next section, you’ll style the website using the Bulma CSS framework, which provides flexbox-based frontend components for building responsive websites.

      Step 5 — Adding a Stylesheet

      At the moment, Eleventy does not recognize CSS files for auto-inclusion in the build directory, so a few extra steps are needed to get this working. Specifically, you’ll need to create a stylesheet directory, and ensure that it is copied over to the build output (_site) when the site is built. You’ll also need to ensure that modifying a stylesheet triggers a rebuild and automatic refreshing in the web browser. You can achieved this by creating a configuration file for Eleventy.

      At the project root, create a css folder followed by a style.css file within it. Open style.css and import the Bulma CSS framework by using the code below:

      eleventy-blog/css/style.css

      @import "https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css";
      

      Save the file.

      Next, create an .eleventy.js file in your project root. This is the configuration file for Eleventy, similar to _config.yml files in Jekyll projects. Note that this file will be hidden in your filesystem since it’s prefixed with a period. You’ll need to use ls -a to get it to show up when listing the directory’s contents.

      Open .eleventy.js in your text editor and paste the following to include the css directory in the build, and also to watch the folder for changes:

      eleventy-blog/.eleventy.js

      module.exports = function (eleventyConfig) {
        // Copy the `css` directory to the output
        eleventyConfig.addPassthroughCopy('css');
      
        // Watch the `css` directory for changes
        eleventyConfig.addWatchTarget('css');
      };
      

      At this point, you need to stop the server with Ctrl+C, and start it again with npm start before the changes take effect. You’ll need to do this every time you modify the configuration file.

      If you check the site in your browser right now, you won’t notice any changes. That’s because the stylesheet has not yet been linked in any template. Go ahead and add it to the base.njk template as shown below.

      eleventy-blog/_includes/layouts/base.njk

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <link rel="stylesheet" href="https://www.digitalocean.com/css/style.css" />
          <title>{{ title }}</title>
        </head>
        <body>
          <header>{% include "nav.njk" %}</header>
          <main>
            {{ content | safe }}
          </main>
        </body>
      </html>
      

      After saving the file, the styles should kick in immediately.

      Styled homepage

      In this step, you added styling to the website using the Bulma CSS framework. In the next step, you’ll expand the site by creating an “About” page.

      Step 6 — Creating an About Page

      At the moment, there is a link to a non-existent “About” page in the navigation menu. You’ll change that by creating a unique layout for all static pages, and afterward, the “About” page itself. In the _includes/layouts folder, create a page.njk file. This will be the layout for all static pages on the site.

      Open the new file in your editor and populate it with the code below. The front matter layout property is used to indicate that the page layout should inherit from the previously created base.njk template. This is known as layout chaining, and it allows us to reuse a template while adding unique elements that are specific to the new template, which helps avoid unnecessary repetition of basic site structures.

      eleventy-blog/_includes/layouts/page.njk

      ---
      layout: layouts/base.njk
      ---
      
      <article class="page-layout">
        <div class="container">
          <div class="columns">
            <div class="column is-8 is-offset-2">
              <div class="content mt-5">
                <h1>{{ title }}</h1>
                {{ content | safe }}
              </div>
            </div>
          </div>
        </div>
      </article>
      

      Now that you have a page layout, you can create the “About” page. To do this, create a directory at the project root called about, and add a new index.md markdown file within it.

      Add the following code into the file:

      eleventy-blog/about/index.md

      ---
      title: About Me
      layout: layouts/page.njk
      ---
      
      I am a person that writes stuff.
      

      After saving the file, go to https://localhost:8080/about. The page should load correctly with the specified layout.

      About page

      Creating other pages, such as a contact page or newsletter page, can be done in the same way: create a directory with the name of the page, then add an index.md file at the root of the new directory. You can also use an HTML or Nunjucks file instead of Markdown if you prefer.

      In this step, you created a unique layout for static pages and added an “About” page to the site. In the next section, you’ll create and process blog posts on an Eleventy website.

      Step 7 — Creating Posts

      Creating a blog post is very similar to creating a page. You’ll start by creating a directory called posts at the project root to keep all posts.

      However, you will create a different layout for posts. It’s going to be similar to the layout for pages, but you will include a date to differentiate it. In a real-world project, it’s likely that you’ll want different layouts for posts and pages, so it’s good practice to create a new layout for each one.

      In the _includes/layouts directory, create a new post.njk file and open it in your text editor. Paste the code below into your post.njk layout file.

      eleventy-blog/_includes/layouts/post.njk

      ---
      layout: layouts/base.njk
      ---
      
      <section class="page-layout">
        <div class="container">
          <div class="columns">
            <div class="column is-8 is-offset-2">
              <article class="content mt-5">
                <h1 class="title">{{ title }}</h1>
                <p class="subtitle is-6">
                  Published on: <time datetime="{{ page.date }}">{{ page.date }}</time>
                </p>
                {{ content | safe }}
              </article>
            </div>
          </div>
        </div>
      </section>
      

      Similar to the page.njk template, the post template extends the base template with additions that make sense for posts (such as the date of publication).

      To use this template, create a new file in your posts directory called first-post.md, and open it in your text editor.

      Paste the following contents into the first-post.md file:

      eleventy-blog/posts/first-post.md

      ---
      title: My First Blog Post
      description: This is the first post on my blog
      tags: post
      date: 2021-06-19
      layout: layouts/post.njk
      ---
      
      You’ll find this post in your `posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `eleventy --serve`, which launches a web server and auto-regenerates your site when a file is updated.
      

      Save the file, then head over to http://localhost:8080/posts/first-post in your browser. Notice how the URL corresponds to the location of the file in the project (excluding the extension). This is how URLs are handled by default, but they can be changed to some other format through the permalink key.

      A blog post

      The post is displayed correctly, but notice how the date is currently formatted. This date format is difficult for users to read, but Eleventy provides no built-in formatting options for dates, unlike most other SSGs. This means that you have to use an external package to get a more human readable format in Eleventy.

      In this step, you created a unique layout for blog posts and added a blog post to the site. In the next section, you’ll create a custom filter that helps with date formatting.

      Step 8 — Using Filters in Eleventy

      Eleventy supports filters for transforming content in various ways. For example, the safe filter used earlier prevents the escaping of HTML content, and there are other built-in ones like slug for transforming text into URL-friendly strings. You can also add your own custom filters that can be used in any template. These customizations can be made through the configuration file.

      Go ahead and add a universal filter for formatting dates so that it can be used in the post template. Start by installing Luxon, a lightweight JavaScript library for date formatting:

      Afterward, open the .eleventy.js config file, and update its contents as follows:

      eleventy-blog/.eleventy.js

      const { DateTime } = require('luxon');
      
      module.exports = function (eleventyConfig) {
        // Copy the `css` directory to the output
        eleventyConfig.addPassthroughCopy('css');
      
        // Watch the `css` directory for changes
        eleventyConfig.addWatchTarget('css');
      
        eleventyConfig.addFilter('readableDate', (dateObj) => {
          return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat(
            'dd LLL yyyy'
          );
        });
      };
      

      The highlighted lines describe how to add a custom filter to Eleventy. First, you need to import whatever objects you need from any external libraries. The DateTime type from Luxon provides several methods for formatting purposes.

      Adding a filter involves calling the addFilter() method provided by the eleventyConfig argument. It takes the filter name as its first argument, and the callback function is what will be executed when the filter is used. In the above snippet, the filter is called readableDate, and the callback function is used to format a date object using the provided date tokens. This will yield a date in the following format: 19 Jul 2021.

      Save the config file and restart the server so that the changes take effect. Then use the readableDate filter in the post template as shown below to format the post date according to the specified layout.

      eleventy-blog/_includes/layouts/post.njk

      . . .
      <p class="subtitle is-6">
        Published on: <time datetime="{{ page.date }}">{{ page.date | readableDate }}</time>
      </p>
      . . .
      

      Once you open the post in your browser, you’ll notice that the date formatting has been updated.

      Screenshot showing nicely formatted date

      In this step, you added a filter to change the date formatting on blog posts. In the next section, you’ll display a list of posts on the homepage, as is conventional on most personal blogs.

      Step 9 — Displaying Posts on the Homepage

      To make it easier for visitors to your site to discover the posts on the blog, it’s a good idea to list them on the homepage. You will use Eleventy’s collections feature in order to implement this functionality.

      Before you proceed to update the index.njk file, you’ll need to create at least three additional posts to showcase on the homepage. You can copy the first post into new files, and change the title and description for each of them. Once you’re through with that, update your index.njk file as shown below:

      eleventy-blog/index.njk

      ---
      title: Homepage
      layout: layouts/base.njk
      ---
      
      <section class="hero is-medium is-primary is-bold">
        <div class="hero-body">
          <div class="container">
            <h1 class="title">{{ site.title }}</h1>
            <h2 class="subtitle">{{ site.description }}</h2>
          </div>
        </div>
      </section>
      
      <section class="postlist mt-3 pt-3">
        <div class="container">
          <h2 class="title has-text-centered mt-3 mb-6">Recent posts</h2>
          <div class="columns">
            {% for post in collections.post | reverse %}
              {% if loop.index0 < 3 %}
                <div class="column">
                  <div class="card">
                    <header class="card-header">
                      <p class="card-header-title">
                        {{ post.data.title }}
                      </p>
                    </header>
                    <div class="card-content">
                      <div class="content">
                        {{ post.data.description }}
                      </div>
                    </div>
                    <footer class="card-footer">
                      <a href="https://www.digitalocean.com/community/tutorials/{{ post.url }}" class="button is-fullwidth is-link card-footer-item">Read article</a>
                    </footer>
                  </div>
                </div>
                {% endif %}
            {% endfor %}
          </div>
        </div>
      </section>
      

      The for loop construct in the above snippet is from the Nunjucks templating language, and its one of the ways to iterate over a collection in Eleventy. It starts with {% for post in collection.post | reverse %} and ends with {% endfor %}. The post collection is created by Eleventy and consists of any page that has its tags front matter property set to post.

      The reverse filter is used here so that the iteration starts from the most recent post instead of the default order, which puts older posts first. Within the loop, the output of the collection is limited to three items, and the post local variable is used to access the title, description, and URL of each post in the collection.

      After saving the file, go to the homepage in the browser to see the results. It should look similar to the screenshot below.

      Eleventy recent posts output

      In this step, you created additional blog posts and used Eleventy’s collections feature to list them on the website’s homepage. You now have a styled website with a homepage, an “About” page, and some posts. Next, you’ll deploy it to production through GitHub and DigitalOcean’s App Platform.

      Step 10 — Pushing the Website to GitHub

      Before you can deploy your code to DigitalOcean’s App Platform, you need to get your site in a Git repository, and push that repository to GitHub. The first thing to do is initialize a Git repo in your project directory:

      Next, create a .gitignore file at the project root so that you can exclude the contents of the node_modules and _site directories from being tracked in the Git repo.

      .gitignore

      node_modules
      _site
      

      After saving the file, run the commands below to add all the project files to the staging area, then make your initial commit:

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

      The output should look similar to this:

      Output

      [master (root-commit) e4e2063] Initial version of the site 15 files changed, 6914 insertions(+) create mode 100644 .eleventy.js create mode 100644 .gitignore create mode 100644 _data/site.json create mode 100644 _includes/layouts/base.njk create mode 100644 _includes/layouts/page.njk create mode 100644 _includes/layouts/post.njk create mode 100644 _includes/nav.njk create mode 100644 about/index.md create mode 100644 css/style.css create mode 100644 index.njk create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 posts/first-post.md create mode 100644 posts/second-post.md create mode 100644 posts/third-post.md

      Navigate to GitHub, log in with your profile, and create a new empty repository for your project called eleventy-blog (it can be public or private). Once the GitHub repo is created, copy the link to the repo, and add it as a remote location for your project in the terminal:

      • git remote add origin https://github.com/username/eleventy-blog.git

      Before you push your changes to the remote location, rename the default branch to main to match what GitHub expects:

      Finally, run the command below to push the main branch to GitHub. Enter your GitHub account credentials when prompted.

      Note: If two-factor authentication is enabled for your GitHub account, you’ll need to use a personal access token or SSH key when accessing GitHub on the command line. For more information, see Using two-factor authentication with the command line.

      The output should look similar to this:

      Output

      Enumerating objects: 23, done. Counting objects: 100% (23/23), done. Delta compression using up to 4 threads Compressing objects: 100% (19/19), done. Writing objects: 100% (23/23), 64.41 KiB | 2.38 MiB/s, done. Total 23 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), done. To https://github.com/username/eleventy-blog.git * [new branch] main -> main

      In this step, you added your site to a Git repository, which you then pushed to GitHub. You are now ready to deploy your website to DigitalOcean’s App Platform.

      Step 11 — Deploying to DigitalOcean with App Platform

      Go ahead and log in to your DigitalOcean account, then head over to https://cloud.digitalocean.com/apps and click the green Create button on the top right. Choose the Apps option in the dropdown, then, on the resulting page, select GitHub as your source.

      DigitalOcean App Platform Source

      You’ll be redirected to GitHub and prompted to give DigitalOcean access to your repositories. You can choose all repositories, or just the ones you wish to deploy. Click Install and Authorize once you’ve made your choice. You should be redirected back to the Choose Source page once again.

      Install and Authorize DigitalOcean on GitHub

      On the Choose Source page, select GitHub, then choose the eleventy-blog repository from the Repository dropdown. Ensure that the selected branch is main, and the checkbox to autodeploy code changes is ticked, then click Next to continue.

      Configure GitHub source

      On the next page, your project will be auto detected as a Node.js project. You may need to change its Type to Static Site, and the Output Directory to _site as shown in the screenshot below. Click Next to continue once everything matches.

      Configure application

      Name your static site, and click Next to go to the Finalize and Launch screen.

      Name static website

      Static sites are free, so retain the default Starter selection under Plans, and press Launch Starter App at the bottom of the page.

      Launch starter app

      Your site will build immediately, and you should see a success message after a few minutes.

      Site deployed to DigitalOcean’s App Platform

      By default, your app will be given a sub-domain on ondigitalocean.app. Now that your site is deployed, you can visit the provided URL to view the live website in your browser. You can also register a custom domain for your site by following How to Manage Domains in App Platform.

      Conclusion

      In this tutorial, you built a static site with Eleventy and deployed to DigitalOcean’s App Platform. Moving forward, you can make changes to your website locally, push the changes to GitHub, and DigitalOcean will automatically update the live site. You can go to the app dashboard to see the progress of the build at any time.

      To build on what you’ve achieved in this tutorial, visit the Eleventy docs to learn more about how you can customize your site’s templates, and add features like responsive images, syntax highlighting, and caching. If you’d like to get started quickly without creating your own theme from scratch, check out the starter projects page.





      Source link

      How To Create Queries in MongoDB


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Documents stored in a MongoDB database can vary widely. Some might be relatively small and contain only a few entries, like items in a shopping list. Others might be highly complex, containing dozens of fields of different types, arrays holding multiple values, and even other documents nested within the larger structure.

      Regardless of how complex your documents are or how many you have, most often you won’t need to review the data in all of them at once. Instead, you’ll more likely want to only retrieve documents that satisfy one or more particular conditions. Similar to how you would find your holiday destination by selecting a range of filters on a booking website, such as distance from the seaside, pet-friendliness, a pool, and nearby parking, you can precisely query MongoDB to find exactly the documents you need. MongoDB provides a robust query mechanism for defining filtering criteria when retrieving documents.

      In this tutorial, you’ll learn how to query MongoDB collections using a different range of filters and conditions. You will also learn what cursors are and how to use them within the MongoDB shell.

      Prerequisites

      To follow this tutorial, you will need:

      Note: The linked tutorials on how to configure your server, install, and then secure MongoDB installation refer to Ubuntu 20.04. This tutorial concentrates on MongoDB itself, not the underlying operating system. It will generally work with any MongoDB installation regardless of the operating system as long as authentication has been enabled.

      Step 1 — Preparing the Sample Database

      To explain how to create queries in MongoDB — including how to filter documents with multiple fields, nested documents, and arrays — this guide uses an example database containing a collection of documents that describe the five highest mountains in the world.

      To create this sample collection, connect to the MongoDB shell as your administrative user. This tutorial follows the conventions of the prerequisite MongoDB security tutorial and assumes the name of this administrative user is AdminSammy and its authentication database is admin. Be sure to change these details in the following command to reflect your own setup, if different:

      • mongo -u AdminSammy -p --authenticationDatabase admin

      When prompted, enter the password you set when you created your administrative user. After providing the password, your prompt will change to a greater-than (>) sign:

      Note: On a fresh connection, the MongoDB shell will automatically connect to the test database by default. You can safely use this database to experiment with MongoDB and the MongoDB shell.

      Alternatively, you could also switch to another database to run all of the example commands given in this tutorial. To switch to another database, run the use command followed by the name of your database:

      To understand how MongoDB filters documents with multiple fields, nested documents and arrays, you’ll need sample data complex enough to allow exploring different types of queries. As mentioned previously, this guide uses a sample collection of the five highest mountains in the world.

      The documents in this collection will follow this format. This example document describes Mount Everest:

      Mount Everest document

      {
          "name": "Everest",
          "height": 8848,
          "location": ["Nepal", "China"],
          "ascents": {
              "first": {
                  "year": 1953,
              },
              "first_winter": {
                  "year": 1980,
              },
              "total": 5656,
          }
      }
      

      This document contains the following fields and values:

      • name: the peak’s name
      • height: the peak’s elevation, in meters
      • location: the countries in which the mountain is located. This field stores values as an array to allow for mountains located in more than one country
      • ascents: this field’s value is another document. When one document is stored within another document like this, it’s known as an embedded or nested document. Each ascents document describes successful ascents of the given mountain. Specifically, each ascents document contains a total field that lists the total number of successful ascents of each given peak. Additionally, each of these nested documents contain two fields whose values are also nested documents:
        • first: this field’s value is a nested document that contains one field, year, which describes the year of the first overall successful ascent
        • first_winter: this field’s value is a nested document that also contains a year field, the value of which represents the year of the first successful winter ascent of the given mountain

      The reason why the first ascents are represented as nested documents even though only the year is included now is to make it easier to expand the ascent details with more fields in the future, such as the summiters’ names or the expedition details.

      Run the following insertMany() method in the MongoDB shell to simultaneously create a collection named peaks and insert five sample documents into it. These documents describe the five tallest mountain peaks in the world:

      • db.peaks.insertMany([
      • {
      • "name": "Everest",
      • "height": 8848,
      • "location": ["Nepal", "China"],
      • "ascents": {
      • "first": {
      • "year": 1953
      • },
      • "first_winter": {
      • "year": 1980
      • },
      • "total": 5656
      • }
      • },
      • {
      • "name": "K2",
      • "height": 8611,
      • "location": ["Pakistan", "China"],
      • "ascents": {
      • "first": {
      • "year": 1954
      • },
      • "first_winter": {
      • "year": 1921
      • },
      • "total": 306
      • }
      • },
      • {
      • "name": "Kangchenjunga",
      • "height": 8586,
      • "location": ["Nepal", "India"],
      • "ascents": {
      • "first": {
      • "year": 1955
      • },
      • "first_winter": {
      • "year": 1986
      • },
      • "total": 283
      • }
      • },
      • {
      • "name": "Lhotse",
      • "height": 8516,
      • "location": ["Nepal", "China"],
      • "ascents": {
      • "first": {
      • "year": 1956
      • },
      • "first_winter": {
      • "year": 1988
      • },
      • "total": 461
      • }
      • },
      • {
      • "name": "Makalu",
      • "height": 8485,
      • "location": ["China", "Nepal"],
      • "ascents": {
      • "first": {
      • "year": 1955
      • },
      • "first_winter": {
      • "year": 2009
      • },
      • "total": 361
      • }
      • }
      • ])

      The output will contain a list of object identifiers assigned to the newly-inserted objects.

      Output

      { "acknowledged" : true, "insertedIds" : [ ObjectId("610c23828a94efbbf0cf6004"), ObjectId("610c23828a94efbbf0cf6005"), ObjectId("610c23828a94efbbf0cf6006"), ObjectId("610c23828a94efbbf0cf6007"), ObjectId("610c23828a94efbbf0cf6008") ] }

      You can verify that the documents were properly inserted by running the find() method with no arguments, which will retrieve all the documents you just added:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } . . .

      With that, you have successfully created the list of example documents of mountains that will serve as the test data for creating queries. Next, you’ll learn how to query with conditions referring to individual fields.

      Step 2 — Querying Individual Fields

      At the end of the previous step, you used MongoDB’s find() method to return every document from the peaks collection. A query like this won’t be very useful in practice, though, as it doesn’t filter any documents and always returns the same result set.

      You can filter query results in MongoDB by defining a specific condition that documents must adhere to in order to be included in a result set. If you have followed the How To Perform CRUD operations in MongoDB tutorial, you have already used the most basic filtering condition: the equality condition.

      As an example, run the following query which returns any documents whose name value is equal to Everest:

      • db.peaks.find(
      • { "name": "Everest" }
      • )

      The second line — { "name": "Everest" } — is the query filter document, a JSON object specifying the filters to apply when searching the collection in order to find documents that satisfy the condition. This example operation tells MongoDB to retrieve any documents in the peaks collection whose name value matches the string Everest:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

      MongoDB returned a single document, as there is only one Mt. Everest in the peaks collection.

      The equality condition specifies a single value that MongoDB will attempt to match against documents in the collection. MongoDB provides comparison query operators that allow you to specify other conditions that also refer to a single field, but filter documents in ways that are more complex than searching for exact matches.

      A comparison operator consists of the operator itself, a single key preceded by a dollar sign ($), and the value the query operator will use to filter documents.

      To illustrate, run the following query which searches for any documents whose name value does not equal Everest:

      • db.peaks.find(
      • { "name": { $ne: "Everest" } }
      • )

      This time, the query filter document includes { $ne: "Everest" }. $ne is the comparison operator in this example, and it stands for “not equal”. The peak name, Everest, appears again as the value for this operator. Because this query is searching for documents whose name value is not equal to Everest, it returns four documents:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } . . .

      The $in operator allows you to write queries that will return documents with values matching one of multiple values held in an array.

      The following example query includes the $in operator, and will return documents whose name value matches either Everest or K2:

      • db.peaks.find(
      • { "name": { $in: ["Everest", "K2"] } }
      • )

      Instead of a single value, the value passed to the $in operator is an array of two peak names in square braces. MongoDB returns two documents, just as expected:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

      The examples so far have queried the name field with text values. You can also filter documents based on numerical values.

      The following example query searches for documents whose height value is greater than 8500:

      • db.peaks.find(
      • { "height": { $gt: 8500 } }
      • )

      This query includes the $gt operator, which stands for greater than. By passing it the value 8500, MongoDB will return documents whose height value is greater than 8500:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

      MongoDB offers a number of comparison query operators in addition to the ones outlined in this section. For a full list of these operators, see the official documentation on the subject.

      Now that you know how to use equality conditions and comparison operators on a single document field, you can move onto learning how to join multiple conditions together in a single query.

      Step 3 — Using Multiple Conditions

      Sometimes, filtering based on a single document field is not enough to precisely select documents of interest. In such cases, you might want to filter documents using multiple conditions at once.

      There are two ways to connect multiple conditions in MongoDB. The first is to use a logical AND conjunction to select documents in the collection matching all the conditions, or the logical OR to select documents matching at least one condition from the list.

      In MongoDB, the AND conjunction is implicit when using more than one field in the query filter document. Try selecting a mountain that matches the name Everest and the exact height of 8848 meters:

      • db.peaks.find(
      • { "name": "Everest", "height": 8848 }
      • )

      Notice that the syntax is similar to the equality condition example from the previous step, but this time two fields appear in the query filter document. MongoDB checks for equality on both fields and requires both to match the requested values in order for a document to be selected:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

      In this case, a single document is returned, but if you try changing the height to any other numerical value the result set will be empty since any returned documents must match both conditions. For instance, the following example will not return any output to the shell:

      • db.peaks.find(
      • { "name": "Everest", "height": 9000 }
      • )

      This implicit AND can be made explicit by including the $and logical query operator followed by a list of conditions that returned documents must satisfy. The following example is essentially the same query as the previous one, but includes the $and operator instead of an implicit AND conjunction:

      • db.peaks.find(
      • { $and: [{"name": "Everest"}, {"height": 8848}] }
      • )

      This time the JSON object containing the $and query operator is the query filter document itself. Here, the comparison operator takes two separate equality conditions that appear in the list, one for name matches and the latter for height matches.

      In order to select documents matching any of the chosen conditions rather than all of them, you can instead use the $or operator:

      • db.peaks.find(
      • { $or: [{"name": "Everest"}, {"name": "K2"}] }
      • )

      When using the $or operator, a document only needs to satisfy one of the two the equality filters:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

      Although each of this example’s conditions are single-field equality conditions, both the $and and $or operators can contain any valid query filter documents. They can even include nested AND/OR condition lists.

      Joining multiple filters together using $and and $or operators as outlined in this step can be very helpful with retrieving fine-grained query results. However, the examples so far have all used query filter documents that filter based on individual values. The next step outlines how to query against values stored in an array field.

      Step 4 — Querying for Array Values

      Sometimes a single field may contain multiple values stored in an array. In our example with mountain peaks, location is such a field. Because mountains often span more than one country, like Kangchenjunga in Nepal and India, a single value may not always be enough for this field.

      In this step, you’ll learn how to construct query filters that match items in array fields.

      Let’s start by trying to select documents representing mountains that are in Nepal. For this example, though, it’s okay if the mountain has multiple locations listed, as long as one of them is Nepal:

      • db.peaks.find(
      • { "location": "Nepal" }
      • )

      This query uses an equality condition that tells MongoDB to return documents whose location value exactly matches the given string value, Nepal, similar to the previous examples that used the name field. MongoDB will select any documents in which the requested value appears in any place in the arrays:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      For this query, MongoDB returned the four documents in which Nepal appears in the location field.

      However, what if you wanted to find mountains located in both China and Nepal? To do this, you could include an array in the filter document, rather than a single value:

      • db.peaks.find(
      • { "location": ["China", "Nepal"] }
      • )

      Even though there are four mountains in Nepal and China in the database, there is only one in which the countries are listed in the order given in this query, so this query returns a single document:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      Notice that the value of the location field for Makalu is identical to the query’s filter document. When you supply an array as the value for the equality condition like this, MongoDB will retrieve documents where the location field matches the query filter exactly, including the order of elements inside the array. To illustrate, run the query again but swap China with Nepal:

      • db.peaks.find(
      • { "location": ["Nepal", "China"] }
      • )

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

      Now, two other mountains are returned, but Makalu is not.

      Using the equality condition like this is not helpful in cases where you care only about elements in an array (regardless of their order) rather than an exact match. Fortunately, MongoDB allows you to retrieve documents containing more than one array element anywhere in an array using the $all query operator.

      To illustrate, run the following query:

      • db.peaks.find(
      • { "location": { $all: ["China", "Nepal"] } }
      • )

      The $all operator will ensure that documents will be checked whether their location array contains both China and Nepal inside in any order. MongoDB will return all three mountains in a single query:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      This step outlined how to use arrays in query filter documents to retrieve documents with more than one value in a single field. If you want to query data held within a nested document, you’ll need to use the special syntax required for such an operation. Continue onto the next step to learn how to do this.

      Step 5 — Querying Fields in Nested Documents

      Recall that the example database documents include an ascent field that holds various details about each mountain’s first ascents an array. This way, the data about the first ascent, the winter ascent, and the total number of ascents is cleanly grouped inside a single nested document. This step explains how you can access fields within a nested document when building queries.

      Review the sample Everest document once more:

      The Everest document

      {
          "name": "Everest",
          "height": 8848,
          "location": ["Nepal", "China"],
          "ascents": {
              "first": {
                  "year": 1953,
              },
              "first_winter": {
                  "year": 1980,
              },
              "total": 5656,
          }
      }
      

      Accessing the name and height fields was straightforward, as a single value resides under these keys. But say you wanted to find the total number of ascents for a given peak. The ascents field contains more data than just the total number of ascents inside. There is a total field, but it’s not part of the main document, so there’s no way to access it directly.

      To solve this issue, MongoDB provides a dot notation to access fields in nested documents.

      To illustrate how MongoDB’s dot notation works, run the following query. This will return all the mountains in the collection that have been ascended more than 1000 times, using the $gt operator highlighted previously:

      • db.peaks.find(
      • { "ascents.total": { $gt: 1000 } }
      • )

      Mt. Everest is the only mountain in the collection with more than 1000 ascents, so only its document will be returned:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

      While the { $gt: 1000 } query filter with $gt operator is familiar, notice how this query accesses the total field held within the document stored in the ascents field. In nested documents, the access path to any given field is constructed with dots indicating the action of going inside the nested object.

      So, ascents.total means that MongoDB should first open the nested document that the ascents field points to and then find the total field within it.

      The notation works with multiple nested documents as well:

      • db.peaks.find(
      • { "ascents.first_winter.year": { $gt: 2000 } }
      • )

      This query will return any documents describing mountains that were first ascended in winter only after the year 2000:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      As before, the ascents.first_winter.year notation means MongoDB first finds the ascents field and finds the nested documents there. It then goes into another nested document, first_winter, and finally retrieves the year field from within it.

      The dot notation can be used to access any depth of nested documents in MongoDB.

      By now you will have a good understanding of how to access data from nested documents and how to filter query results. You can move on to learning how to limit the list of fields returned by your queries.

      Step 6 — Returning a Subset of Fields

      In all the examples so far, whenever you queried the peaks collection, MongoDB returned one or more full documents. Oftentimes, you’ll only need information from a handful of fields. As an example, you might only want to find the names of the mountains in the database.

      This isn’t just a matter of legibility, but also of performance. If only a small part of a document is needed, retrieving whole document objects would be an unnecessary performance burden on the database. This may not be a problem when working with small datasets like this tutorial’s examples, but it becomes an important consideration when working with many large, complex documents.

      As an example, say you’re only interested in mountain names stored in the peaks collection, but the ascent details or location are not important this time. You could limit the fields your query will return by following the query filter document with a projection.

      A projection document is a JSON object where keys correspond to the fields of the queried documents. Projections can be either constructed as inclusion projections or exclusion projections. When the projection document contains keys with 1 as their values, it describes the list of fields that will be included in the result. If, on the other hand, projection keys are set to 0, the projection document describes the list of fields that will be excluded from the result.

      Run the following query, which includes the by-now familiar find() method. This query’s find() method includes two arguments, instead of one. The first, {}, is the query filter document. Here it’s an empty JSON object, meaning it won’t apply any filtering. The second argument, { "name": 1 }, describes the projection and means that the query results will only include each document’s name field:

      • db.peaks.find(
      • {},
      • { "name": 1 }
      • )

      After running this example query, MongoDB returns the following results:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest" } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2" } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga" } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse" } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu" }

      Notice that the returned documents are simplified, and contain only the name and _id fields. MongoDB always includes the _id key, even if it’s not explicitly requested.

      To illustrate how to specify what fields to exclude, run the following query. It will return data from each document, but will exclude the ascents and location fields:

      • db.peaks.find(
      • {},
      • { "ascents": 0, "location": 0 }
      • )

      MongoDB returns all five mountains once again, but this time only the name, height, and _id fields are present:

      Output

      { "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848 } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611 } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586 } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516 } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485 }

      Note: When specifying projections, you cannot mix inclusions and exclusions. You either have to specify the list of fields to include, or a list of fields to exclude.

      There is, however, one exception to this rule. MongoDB allows you to exclude the _id field from a result set even when the query has an inclusion projection applied. To suppress the _id field, you can append "_id": 0 to the projection document. The following example is similar to the previous example query, but will exclude every field, including _id, except for the name field:

      • db.peaks.find(
      • {},
      • { "_id": 0, "name": 1 }
      • )

      Output

      { "name" : "Everest" } { "name" : "K2" } { "name" : "Kangchenjunga" } { "name" : "Lhotse" } { "name" : "Makalu" }

      Projections can also be used to include or exclude fields in nested documents. Say, for example, that you want to know each mountain’s first winter ascent and the total number of ascents, both of which are nested within the ascents field. Additionally, you want to return each mountain’s name. To do this, you could run a query like this:

      • db.peaks.find(
      • {},
      • { "_id": 0, "name": 1, "ascents": { "first_winter": 1, "total": 1 } }
      • )

      Notice how the projection is specified for the ascents fields and how it follows the structure of the nested document, being a nested projection itself. By using "first_winter": 1, "total": 1 this query tells the database to include only these two fields from the nested document and no other.

      The returned documents will contain only the requested fields:

      Output

      { "name" : "Everest", "ascents" : { "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "name" : "K2", "ascents" : { "first_winter" : { "year" : 1921 }, "total" : 306 } } { "name" : "Kangchenjunga", "ascents" : { "first_winter" : { "year" : 1988 }, "total" : 461 } } { "name" : "Makalu", "ascents" : { "first_winter" : { "year" : 2009 }, "total" : 361 } }

      Limiting the size of returned documents to only a subset of fields can be helpful with making result sets more readable and can even improve performance. The next step outlines how to limit the number of documents returned by a query, and also details how to sort the data returned by a query.

      Step 7 — Using Cursors to Sort and Limit Query Results

      When retrieving objects from a large collection, there may be times when you want to limit the number of results or perhaps sort them in a particular order. For example, a popular approach for shopping sites is to sort products by their price. MongoDB uses cursors which allow you to limit the number of documents returned in a query result set and also sort the results in ascending or descending order.

      Recall this example query from Step 1:

      You may recall that the result set returned by this query includes all the data from each document in the peaks collection. While it may seem like MongoDB returns all the objects from the peaks collection, this is not the case. What MongoDB actually returns is a cursor object.

      A cursor is a pointer to the result set of a query but it is not the result set itself. It’s an object that can be iterated, meaning that you can request the cursor to return the next document in line, and only then will the full document be retrieved from the database. Until that happens, the cursor only points to the next document on the list.

      With cursors, MongoDB can ensure that the actual document retrieval happens only when it’s needed. This can have significant performance implications when the documents in question are large or many of them are requested at once.

      To illustrate how cursors work, run the following operation which includes both the find() and count() methods:

      MongoDB will respond with 5:

      Output

      5

      Under the hood, the find() method finds and then returns a cursor, and then the count() method is called on that cursor. This lets MongoDB know that you’re interested in the object count and not the documents themselves. This means that documents won’t be a part of the results — all the database will return is the count. Using methods on the cursor object to further modify the query before retrieving documents from the cursor, you can ensure only the database operations that you ask for will be performed on the collection.

      Note: When executing queries, the MongoDB shell automatically iterates over the returned cursors 20 times so as to display the first 20 results on the screen. This is specific to the MongoDB shell. When working with MongoDB programmatically, it won’t immediately retrieve any results from a cursor.

      Another MongoDB method that uses cursors to alter a result set is the limit() method. As its name implies, you can use limit() to limit the number of results a query will return.

      Run the following query which will retrieve only three mountain peaks from the collection:

      • db.peaks.find(
      • {},
      • { "_id": 0, "name": 1, "height": 1 }
      • ).limit(3)

      MongoDB shell will respond with three objects rather than five, even though the query isn’t filtering any data:

      Output

      { "name" : "Everest", "height" : 8848 } { "name" : "K2", "height" : 8611 } { "name" : "Kangchenjunga", "height" : 8586 }

      The limit(3) method applied on the cursor tells the cursor to stop returning further documents after reaching the first 3. Using the limit() cursor method like this with large collections will help to ensure that you only retrieve the results you need and no more.

      By default, MongoDB will return objects in the order of their insertion, but you might want to alter that behavior. Say you’re interested in finding the three lowest mountain peaks held in the database. You could run the following query:

      • db.peaks.find(
      • {},
      • { "_id": 0, "name": 1, "height": 1 }
      • ).limit(3).sort({ "height": 1 })

      The added sort({ "height": 1 }) causes the result set to differ from the previous example:

      Output

      { "name" : "Makalu", "height" : 8485 } { "name" : "Lhotse", "height" : 8516 } { "name" : "Kangchenjunga", "height" : 8586 }

      Again, only three mountain peaks are returned. However, this time they have been sorted ascending from the one with the lowest height value.

      The sort() method on the cursor accepts a JSON object — height — as an argument, similar to the projection document. It also accepts the list of keys that will be used to sort against. The accepted value is either 1 for ascending or -1 for descending sort order for each key.

      Conclusion

      By reading this article, you familiarized yourself with the way MongoDB uses to filter query results. You filtered collection documents against individual fields, multiple conditions, and complex structures such as arrays and nested documents. You have also learned to select only a subset of fields and sort the results using cursor methods. These techniques can be used to retrieve only documents of interest from otherwise large collections.

      The tutorial described only a handful of query operators put forward by MongoDB to allow precise document querying. You can study the official official MongoDB documentation to learn more about different query operators.



      Source link

      How To Create Your First Web Application Using Flask and Python 3


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a lightweight Python web framework that provides useful tools and features for creating web applications in the Python Language. It gives developers flexibility and is an accessible framework for new developers because you can build a web application quickly using only a single Python file. Flask is also extensible and doesn’t force a particular directory structure or require complicated boilerplate code before getting started.

      Learning Flask will allow you to quickly create web applications in Python. You can take advantage of Python libraries to add advanced features to your web application, like storing your data in a database, or validating web forms.

      In this tutorial, you’ll build a small web application that renders HTML text on the browser. You’ll install Flask, write and run a Flask application, and run the application in development mode. You’ll use routing to display various web pages that serve different purposes in your web application. You’ll also use view functions to allow users to interact with the application through dynamic routes. Finally, you’ll use the debugger to troubleshoot errors.

      Prerequisites

      Step 1 — Installing Flask

      In this step, you’ll activate your Python environment and install Flask using the pip package installer.

      First, activate your programming environment if you haven’t already:

      Once you have activated your programming environment, install Flask using the pip install command:

      Once the installation is complete, you will see a list of installed packages in the last parts of the output, similar to the following:

      Output

      ... Installing collected packages: Werkzeug, MarkupSafe, Jinja2, itsdangerous, click, flask Successfully installed Jinja2-3.0.1 MarkupSafe-2.0.1 Werkzeug-2.0.1 click-8.0.1 flask-2.0.1 itsdangerous-2.0.1

      This means that installing Flask also installed several other packages. These packages are dependencies Flask needs to perform different functions.

      You’ve created the project folder, a virtual environment, and installed Flask. You can now move on to setting up a simple application.

      Step 2 — Creating a Simple Application

      Now that you have your programming environment set up, you’ll start using Flask. In this step, you’ll make a small Flask web application inside a Python file, in which you’ll write HTML code to display on the browser.

      In your flask_app directory, open a file named app.py for editing, use nano or your favorite text editor:

      Write the following code inside the app.py file:

      flask_app/app.py

      
      from flask import Flask
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return '<h1>Hello, World!</h1>'
      

      Save and close the file.

      In the above code block, you first import the Flask object from the flask package. You then use it to create your Flask application instance, giving it the name app. You pass the special variable __name__, which holds the name of the current Python module. This name tells the instance where it’s located; you need this because Flask sets up some paths behind the scenes.

      Once you create the app instance, you can use it to handle incoming web requests and send responses to the user. @app.route is a decorator that turns a regular Python function into a Flask view function, which converts the function’s return value into an HTTP response to be displayed by an HTTP client, such as a web browser. You pass the value '/' to @app.route() to signify that this function will respond to web requests for the URL /, which is the main URL.

      The hello() view function returns the string '<h1>Hello, World!</h1>' as an HTTP response.

      You now have a simple Flask application in a Python file called app.py, in the next step, you will run the application to see the result of the hello() view function rendered in a web browser.

      Step 3 — Running the Application

      After creating the file that contains the Flask application, you’ll run it using the Flask command line interface to start the development server and render on the browser the HTML code you wrote as a return value for the hello() view function in the previous step.

      First, while in your flask_app directory with your virtual environment activated, tell Flask where to find the application (app.py in your case) using the FLASK_APP environment variable with the following command (on Windows, use set instead of export):

      Then specify that you want to run the application in development mode (so you can use the debugger to catch errors) with the FLASK_ENV environment variable:

      • export FLASK_ENV=development

      Lastly, run the application using the flask run command:

      Once the application is running, the output will be something like this:

      Output

      * Serving Flask app "app" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 296-353-699

      The preceding output has several pieces of information, such as:

      • The name of the application you’re running ("app").
      • The environment in which the application is being run (development).
      • Debug mode: on signifies that the Flask debugger is running. This is useful when developing because it provides detailed error messages when things go wrong, which makes troubleshooting easier.
      • The application is running locally on the URL http://127.0.0.1:5000/. 127.0.0.1 is the IP that represents your machine’s localhost and :5000 is the port number.

      Open a browser and type in the URL http://127.0.0.1:5000/. You will see the text Hello, World! in an <h1> heading as a response. This confirms that your application is successfully running.

      If you want to stop the development server, press CTRL+C.

      Warning: Flask uses a simple web server to serve your application in a development environment, which also means that the Flask debugger is running to make catching errors easier. You should not use this development server in a production deployment. See the Deployment Options page on the Flask documentation for more information. You can also check out this Flask deployment tutorial with Gunicorn or this one with uWSGI or you can use DigitalOcean App Platform to deploy your Flask application by following the How To Deploy a Flask App Using Gunicorn to App Platform tutorial.

      To continue developing the app.py application, leave the development server running and open another terminal window. Move into the flask_app directory, activate the virtual environment, set the environment variables FLASK_ENV and FLASK_APP, and continue to the next steps. (These commands are listed earlier in this step.)

      Note: When opening a new terminal, or when you close the one you are running the development server on and want to rerun it, it is important to remember activating the virtual environment and setting the environment variables FLASK_ENV and FLASK_APP for the flask run command to work properly.

      You only need to run the server once in one terminal window.

      While a Flask application’s development server is already running, it is not possible to run another Flask application with the same flask run command. This is because flask run uses the port number 5000 by default, and once it is taken, it becomes unavailable to run another application on so you would receive an error similar to the following:

      Output

      OSError: [Errno 98] Address already in use

      To solve this problem, either stop the server that’s currently running via CTRL+C, then run flask run again, or if you want to run both applications at the same time, you can pass a different port number to the -p argument, for example, to run another application on port 5001 use the following command:

      With this you can have one application running on http://127.0.0.1:5000/ and another one on http://127.0.0.1:5001/ if you want to.

      You now have a small Flask web application. You’ve run your application and displayed information on the web browser. Next, you’ll learn about routes and how to use them to serve multiple web pages.

      Step 4 — Routes and View Functions

      In this step, you’ll add a few routes to your application to display different pages depending on the requested URL. You’ll also learn about view functions and how to use them.

      A route is a URL you can use to determine what the user receives when they visit your web application on their browser. For example, http://127.0.0.1:5000/ is the main route that might be used to display an index page. The URL http://127.0.0.1:5000/about may be another route used for an about page that gives the visitor some information about your web application. Similarly, you can create a route that allows users to sign in to your application at http://127.0.0.1:5000/login.

      Your Flask application currently has one route that serves users who request the main URL (http://127.0.0.1:5000/). To demonstrate how to add a new web page to your application, you will edit your application file to add another route that provides information on your web application at http://127.0.0.1:5000/about.

      First, open your app.py file for editing:

      Edit the file by adding the following highlighted code at the end of the file:

      flask_app/app.py

      from flask import Flask
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return '<h1>Hello, World!</h1>'
      
      
      @app.route('/about/')
      def about():
          return '<h3>This is a Flask web application.</h3>'
      

      Save and close the file.

      You added a new function called about(). This function is decorated with the @app.route() decorator that transforms it into a view function that handles requests for the http://127.0.0.1:5000/about endpoint.

      With the development server running, visit the following URL using your browser:

      http://127.0.0.1:5000/about
      

      You will see the text This is a Flask web application. rendered in an <h3> HTML heading.

      You can also use multiple routes for one view function. For example, you can serve the index page at both / and /index/. To do this, open your app.py file for editing:

      Edit the file by adding another decorator to the hello() view function:

      flask_app/app.py

      from flask import Flask
      
      app = Flask(__name__)
      
      @app.route('/')
      @app.route('/index/')
      def hello():
          return '<h1>Hello, World!</h1>'
      
      @app.route('/about/')
      def about():
          return '<h3>This is a Flask web application.</h3>'
      

      Save and close the file.

      After adding this new decorator, you can access the index page at both http://127.0.0.1:5000/ and http://127.0.0.1:5000/index.

      You now understand what routes are, how to use them to make view functions, and how to add new routes to your application. Next, you’ll use dynamic routes to allow users to control the application’s response.

      Step 5 — Dynamic Routes

      In this step, you’ll use dynamic routes to allow users to interact with the application. You’ll make a route that capitalizes words passed through the URL, and a route that adds two numbers together and displays the result.

      Normally, users don’t interact with a web application by manually editing the URL. Rather, the user interacts with elements on the page that lead to different URLs depending on the user’s input and action, but for the purposes of this tutorial, you will edit the URL to demonstrate how to make the application respond differently with different URLs.

      First, open your app.py file for editing:

      If you allow the user to submit something to your web application, such as a value in the URL as you are going to do in the following edit, you should always keep in mind that your app should not directly display untrusted data (data the user submits). To display user data safely, use the escape() function that comes with the markupsafe package, which was installed along with Flask.

      Edit app.py and add the following line to the top of the file, above the Flask import:

      flask_app/app.py

      from markupsafe import escape
      from flask import Flask
      
      # ...
      

      Then, add the following route to the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/capitalize/<word>/')
      def capitalize(word):
          return '<h1>{}</h1>'.format(escape(word.capitalize()))
      

      Save and close the file.

      This new route has a variable section <word>. This tells Flask to take the value from the URL and pass it to the view function. The URL variable <word> passes a keyword argument to the capitalize() view function. The argument has the same name as the URL variable (word in this case). With this you can access the word passed through the URL and respond with a capitalized version of it using the capitalize() method in Python.

      You use the escape() function you imported earlier to render the word string as text. This is important to avoid Cross Site Scripting (XSS) attacks. If the user submits malicious JavaScript instead of a word, escape() will it render as text and the browser will not run it, keeping your web application safe.

      To display the capitalized word inside an <h1> HTML heading, you use the format() Python method, for more on this method, see How To Use String Formatters in Python 3

      With the development server running, open your browser and visit the following URLs. You can replace the highlighted words with any word of your choice.

      http://127.0.0.1:5000/capitalize/hello
      http://127.0.0.1:5000/capitalize/flask
      http://127.0.0.1:5000/capitalize/python
      

      You can see the word in the URL capitalized in an <h1> tag on the page.

      You can also use multiple variables in a route. To demonstrate this, you will add a route that adds two positive integer numbers together and displays the result.

      Open your app.py file for editing:

      Add the following route to the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/add/<int:n1>/<int:n2>/')
      def add(n1, n2):
          return '<h1>{}</h1>'.format(n1 + n2)
      

      Save and close the file.

      In this route, you use a special converter int with the URL variable (/add/<int:n1>/<int:n2>/) which only accepts positive integers. By default, URL variables are assumed to be strings and are treated as such.

      With the development server running, open your browser and visit the following URL:

      http://127.0.0.1:5000/add/5/5/
      

      The result will be the sum of the two numbers (10 in this case).

      You now have an understanding of how to use dynamic routes to display different responses in a single route depending on the requested URL. Next, you’ll learn how to troubleshoot and debug your Flask application in case of an error.

      Step 6 — Debugging A Flask Application

      When developing a web application, you will frequently run into situations where the application displays an error instead of the behavior you expect. You may misspell a variable or forget to define or import a function. To make fixing these problems easier, Flask provides a debugger when running the application in development mode. In this step, you will learn how to fix errors in your application using the Flask debugger.

      To demonstrate how to handle errors, you will create a route that greets a user from a list of usernames.

      Open your app.py file for editing:

      Add the following route to the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/users/<int:user_id>/')
      def greet_user(user_id):
          users = ['Bob', 'Jane', 'Adam']
          return '<h2>Hi {}</h2>'.format(users[user_id])
      

      Save and close the file.

      In the route above, the greet_user() view function receives a user_id argument from the user_id URL variable. You use the int converter to accept positive integers. Inside the function, you have a Python list called users, which contains three strings representing usernames. The view function returns a string that is constructed depending on the provided user_id. If the user_id is 0, the response will be Hi Bob in an <h2> tag because Bob is the first item in the list (the value of users[0]).

      With the development server running, open your browser and visit the following URLs:

      http://127.0.0.1:5000/users/0
      http://127.0.0.1:5000/users/1
      http://127.0.0.1:5000/users/2
      

      You will receive the following responses:

      Output

      Hi Bob Hi Jane Hi Adam

      This works well so far, but it can go wrong when you request a greeting for a user who doesn’t exist. To demonstrate how the Flask debugger works, visit the following URL:

      http://127.0.0.1:5000/users/3
      

      You’ll see a page that looks like this:

      Flask Debugger

      At the top, the page gives you the name of the Python exception, which is IndexError, indicating that the list index (3 in this case) is out of the list’s range (which is only from 0 to 2 because the list has only three items). In the debugger, you can see the traceback that tells you the lines of code that raised this exception.

      The last two lines of the traceback usually give the source of the error. In your case the lines may be something like the following:

      File "/home/USER/flask_app/app.py", line 28, in greet_user
          return '<h2>Hi {}</h2>'.format(users[user_id])
      

      This tells you that the error originates from the greet_user() function inside the app.py file, specifically in the return line.

      Knowing the original line that raises the exception will help you determine what went wrong in your code, and decide what to do to fix it.

      In this case you can use a simple try...except clause to fix this error. If the requested URL has an index outside the list’s range, the user will receive a 404 Not Found error, which is an HTTP error that tells the user the page they are looking for does not exist.

      Open your app.py file for editing:

      To respond with an HTTP 404 error, you will need Flask’s abort() function, which can be used to make HTTP error responses. Change the second line in the file to also import this function:

      flask_app/app.py

      from markupsafe import escape
      from flask import Flask, abort
      

      Then edit the greet_user() view function to look as follows:

      flask_app/app.py

      # ...
      
      @app.route('/users/<int:user_id>/')
      def greet_user(user_id):
          users = ['Bob', 'Jane', 'Adam']
          try:
              return '<h2>Hi {}</h2>'.format(users[user_id])
          except IndexError:
              abort(404)
      

      You use try above to test the return expression for errors. If there was no error, meaning that user_id has a value that matches an index in the users list, the application will respond with the appropriate greeting. If the value of user_id is outside the list’s range, an IndexError exception will be raised, and you use except to catch the error and respond with an HTTP 404 error using the abort() Flask helper function.

      Now, with the development server running, visit the URL again:

      http://127.0.0.1:5000/users/3
      

      This time you’ll see a standard 404 error page informing the user that the page does not exist.

      By the end of this tutorial, your app.py file will look like this:

      flask_app/app.py

      from markupsafe import escape
      from flask import Flask, abort
      
      app = Flask(__name__)
      
      
      @app.route('/')
      @app.route('/index/')
      def hello():
          return '<h1>Hello, World!</h1>'
      
      
      @app.route('/about/')
      def about():
          return '<h3>This is a Flask web application.</h3>'
      
      @app.route('/capitalize/<word>/')
      def capitalize(word):
          return '<h1>{}</h1>'.format(escape(word.capitalize()))
      
      @app.route('/add/<int:n1>/<int:n2>/')
      def add(n1, n2):
          return '<h1>{}</h1>'.format(n1 + n2)
      
      @app.route('/users/<int:user_id>/')
      def greet_user(user_id):
          users = ['Bob', 'Jane', 'Adam']
          try:
              return '<h2>Hi {}</h2>'.format(users[user_id])
          except IndexError:
              abort(404)
      

      You now have a general idea of how to use the Flask debugger to troubleshoot your errors and help you determine the appropriate course of action to fix them.

      Conclusion

      You now have a general understanding of what Flask is, how to install it, and how to use it to write a web application, how to run the development server, and how to use routes and view functions to display different web pages that serve specific purposes. You’ve also learned how to use dynamic routes to allow users to interact with your web application via the URL, and how to use the debugger to troubleshoot errors.

      If you would like to read more about Flask, check out the Flask topic page.



      Source link