Build an Editorial Website with vanilla JavaScript and Strapi

Shada - Nov 22 '21 - - Dev Community

An editorial website is a site that publishes written, visual, or audio content. The most popular editorial websites tend to be online magazines and newspapers. Just making and publishing some form of content may seem straightforward, but hosting, managing, and delivering is challenging. You need to have a user-friendly platform to create, organize, and store the content on. You also need to easily manage who has access to it, what structure it should take, how it should be made available to various external platforms, etc. This is where a content management system (CMS) can come in handy.

In this tutorial, you will learn how to create an editorial website using Strapi and vanilla JavaScript. Strapi is a headless CMS that helps you create, manage, and deliver content. It provides an admin panel where you can input and configure your content. Additionally, it supports data like rich text, images, video, audio, etc. It also exposes a customizable API that makes it easy for external sites to consume your content.

Goals

By the end of this tutorial, you should have learned how to:

  1. Install a Strapi application.
  2. Create an article content type.
  3. Add sample articles to display.
  4. Clone an editorial starter app and configure it.
  5. Fetch and display a single article or a list of articles from Strap on an editorial app.
  6. Display markdown content on a HTML page.

Prerequisites

To build this project, you will need:

  1. Node.js v14.x or lower. Strapi requires it as a dependency. You can get a v14.x release from the Node.js releases page. Pick an appropriate installer for your OS.
  2. the Strapi CLI installed. You will need this to create a Strapi Strapi app and generate APIs and components. You can get it by running:
    npm i strapi -g
Enter fullscreen mode Exit fullscreen mode
  1. Git installed. You will need this to clone the editorial app starter. This guide explains how to install it on your specific operating system.
  2. Basic knowledge of JavaScript and Markdown. You can learn more about JavaScript from this basics tutorial and Markdown from this extensive guide.

Step 1 - Install and Set Up a Strapi Application

In this step, you will install the Strapi app and set up an administrator account on its admin panel. The Strapi app will be named editorial-strapi-app. To create the Strapi app, switch directories to where you’d like the app to reside. Run the following command on your terminal:

    strapi new editorial-strapi-app --quickstart
Enter fullscreen mode Exit fullscreen mode

Running this command will create the Strapi app, install its dependencies, and start it. The --quickstart flag will generate the Strapi app using Strapi’s quickstart system. The Strapi app will use SQLite as a database.

Once the installation completes, you will be redirected to the Strapi admin panel at http://localhost:1337/admin/auth/register-admin. Here you will have to create an administrator account before you can proceed. Fill the page and create an account.

Strapi Sign Up Page to create an administrator account

After you sign up, you will be redirected to the admin panel. This is where you will add content, media, and content types. You can also manage Strapi app and plugin settings on the panel.

Strapi Admin Panel

By the end of this step, you should have installed the Strapi app and created an administrator account. You should have also accessed the admin panel. In the next step, you will generate an article API.

Step 2 - Generate the Article API

To create an article on Strapi, you first need to set up a content type for it. In this step, you will not only create an article content type but generate a whole API for it. First, you need to decide what attributes the article content type will have. For this tutorial, it will have 5:

Field Name Field Type Field Description
Title String This is the title of the article.
Cover Image Media This is the main image displayed for the article.
Description Text This is a brief introduction of what the article is all about.
Content Rich Text This is the actual content of the article in markdown format.
Tags Component These are the tags associated with the article.

To generate the article API, stop the running Strapi app on the command line. Then run this command:

    mkdir -p components/tag && touch components/tag/tags.json
Enter fullscreen mode Exit fullscreen mode

Running this command will create the tags component which you will later use when creating the article API. In components/tag/tags.json, add the following:

    {
      "collectionName": "components_tag_tags",
      "info": {
        "name": "Tags",
        "icon": "tags",
        "description": ""
      },
      "options": {},
      "attributes": {
        "name": {
          "type": "string",
          "required": true,
          "unique": true
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

A tag will have one attribute: name. Each tag name needs to be unique and non-empty, that’s why required and unique are set to true.

Next you will generate the article API. Run this command:

    strapi generate:api article content:richtext title:string description:text
Enter fullscreen mode Exit fullscreen mode

This command will generate an article API. It will create the article model, its routes, controllers, and services. You’ll notice that a new folder has been added at api/article. This is where this API resides.

Running this command is limiting as you are unable to specify finer details for the attributes like whether its required. You will have to modify the attributes in the model settings file, at api/article/models/article.settings.json. Since it’s not possible to specify a media or component type with this command, you will also have to add the missing cover_image and tags attributes. In api/article/models/article.settings.json add the following:

    {
      "kind": "collectionType",
      "collectionName": "articles",
      "info": {
        "name": "article",
        "description": "Articles for the editorial site"
      },
      "options": {
        "increments": true,
        "timestamps": true,
        "draftAndPublish": true
      },
      "pluginOptions": {},
      "attributes": {
        "content": {
          "type": "richtext",
          "required": true
        },
        "cover_image": {
          "model": "file",
          "via": "related",
          "allowedTypes": [
            "images"
          ],
          "plugin": "upload",
          "required": true,
          "pluginOptions": {}
        },
        "description": {
          "type": "text",
          "required": true
        },
        "tags": {
          "type": "component",
          "repeatable": true,
          "component": "tag.tags"
        },
        "title": {
          "type": "string",
          "required": true,
          "unique": true
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

Notice how most of the fields are now required and that the tags and cover_image attributes are now included in the attributes. The tags attribute is marked as repeatable so you can add multiple tags to an article. Now you can start the Strapi app to confirm that the article content type has been added. Do this by running:

    npm run develop
Enter fullscreen mode Exit fullscreen mode

In this step, you generated the articles API. In the next one, you will make the routes created in this step public.

Step 3 - Make Article Routes Public

When you generate the article API, six routes are created. These routes expose functionality to:

- create an article
- update an article
- delete an article
- return an article using its ID
- return all articles
- return a count of articles. 
Enter fullscreen mode Exit fullscreen mode

However, all these routes are made private and will return a 403 forbidden error when called. For this tutorial, you will need two routes: one that returns an article given its ID and another that returns a list of articles. These are the routes that you shall make public in this tutorial. Here’s how you will do this:

  1. To begin, log on to the admin panel.
  2. Follow this link to the Public Roles settings. You can also find it under the Users and Permissions Plugin settings. All these are on the Settings page.
  3. Under the Permissions section, look for the Application Subsection.
  4. Under the Article category, select the findone and find checkboxes.
  5. Then click the bright green Save button in the top right corner of the page.

Strapi Article Routes Permission Settings

That’s it. Now the /articles and /articles/:id routes are accessible. However, since you haven’t added any sample articles yet, they won’t be able to return any data.

In this step, you made the articles and articles/:id routes public. In the next step, you shall add sample articles for the routes to return.

Step 4 - Add Sample Articles

In this step, you will create sample articles that the public API routes will return. You will do this by:

  1. Going to the Create an entry article form.
  2. Enter all the fields with sample data. Keep in mind that:
    1. All the fields are required apart from tags. So you cannot create an entry without all of them specified.
    2. You can use this lorem ipsum markdown generator if you’d like sample content for your article. You can copy the markdown from that page and add it to the content field.
  3. Once you’re done entering all the fields, click the bright green Save button in the top right corner of the form.

Make at least three articles to use for your editorial site. Here’s what that form should look like.

Article Entry Creation Form

In this step, you created at least three articles to display on your editorial website. In the next step, you will clone a starter editorial app and fetch the articles from the Strapi app.

Step 5 - Clone the Editorial Site Starter

The editorial site that will display the articles is made from HTML, CSS, and vanilla JavaScript. No framework will be used in it. In this step, you will clone a starter editorial app from Github that contains all the styling and HTML markup. All you will have to do in the subsequent steps is to add the vanilla JavaScript files to fetch the articles from Strapi.

Pick a directory where you want to put the editorial site. Run this command on your terminal:

    git clone https://github.com/zaracooper/editorial-app.git
Enter fullscreen mode Exit fullscreen mode

Once the cloning is complete, switch directories into editorial-app.

    cd editorial-app
Enter fullscreen mode Exit fullscreen mode

The app has two branches: main which is the complete app and starter which is what you will build off of. Switch branches to starter by running:

    git checkout starter
Enter fullscreen mode Exit fullscreen mode

Next, install the app’s dependencies.

    npm install
Enter fullscreen mode Exit fullscreen mode

The app uses a couple of dependencies: [lite-server](https://www.npmjs.com/package/lite-server) to serve the app and [showdown](https://www.npmjs.com/package/showdown) to convert markdown to HTML. These are what are installed above.

You can now run the server. Note that the scripts that fetch the articles from Strapi and populate the pages with them are still empty. So the pages will be mostly blank apart from a few titles. To run the app use:

    npm run dev
Enter fullscreen mode Exit fullscreen mode

Running the above command will launch the app in a browser at http://localhost:3000. When you make any changes, lite-server will reload the open pages. Now you can start adding code to fetch the articles from Strapi.

In this step, you cloned a starter editorial app from GitHub, installed its dependencies, and launched it. In the proceeding step, you will fetch a list of sample articles you created from Strapi and display them on the home page.

Step 6 - Add an Article List

The home page, index.html, displayed at http://localhost:3000 is pretty bare. During this step, you will fetch the articles from http://localhost:1337/articles and display them on the home page.

The script for the home page is located at scripts/index.js. It has two functions:

  • fetchArticles() which fetches articles from the Strapi /articles route.
  • createArticleCard() which creates a card on the home page for each article.

Here’s the code for the scripts/index.js file. It is the script for the index.html page. Copy this code into the scripts/index.js file.

    // scripts/index.js
    function fetchArticles() {
        const articlesReq = new Request('http://localhost:1337/articles');

        fetch(articlesReq)
            .then(response => response.json())
            .then(articles => {
                let articleList = document.getElementById("article-list");

                articles.forEach(article => {
                    articleList.appendChild(createArticleCard(article));
                });
            });
    }

    function createArticleCard(article) {
        let card = document.createElement("div");
        card.classList.add("card");
        card.onclick = () => {
            window.location.replace(`/pages/article.html?id=${article.id}`)
        };

        let cardImage = document.createElement("img");
        cardImage.classList.add("card-img");
        cardImage.src = `http://localhost:1337${article.cover_image.formats.thumbnail.url}`;

        let cardBody = document.createElement("div");
        cardBody.classList.add("card-body");

        let articleTitle = document.createElement("p");
        articleTitle.classList.add("card-title");
        articleTitle.innerHTML = article.title;

        let articleDescription = document.createElement("div");
        articleDescription.classList.add("card-description");
        articleDescription.innerHTML = article.description;

        let articleTags = document.createElement("div");
        articleTags.classList.add("article-tag-cont");

        let tag;

        article.tags.forEach(tg => {
            if (tg.name) {
                tag = document.createElement("span")
                tag.classList.add("article-tag");
                tag.innerHTML = tg.name;

                articleTags.appendChild(tag);
            }
        });

        cardBody.append(articleTitle, articleDescription, articleTags);

        card.append(cardImage, cardBody);

        return card;
    }

    fetchArticles();
Enter fullscreen mode Exit fullscreen mode

fetchArticles() makes a request to http://localhost:1337 using the fetch API and gets a JSON list of available articles. It then gets the article-list div element where all the cards are to be added. For each article in the list, it calls createArticleCard(article) to create a card. It then adds the card to the article-list div. fetchArticles() is called when the file is loaded.

createArticleCard() creates a card for each article. It adds the image, title, description, and tags to the card. It also makes the card clickable so that when it is selected, the user is redirected to the pages/article.html page with the article id as a query parameter. The pages/article.html page displays the actual content of the article as you will see in the next step.

Here is a screenshot of what this page should look like.

Editorial App Home Page

In this step, you added code to the index page script to fetch articles from Strapi and populate them on the home page. In the step that follows, you will fetch a single article from Strapi and display it on the article page.

Step 7 - Fetch a Single Article

The last thing to do is display the content of an article. Using the id passed as a query parameter, you will fetch the article from the Strapi app. You will then convert the markdown content to HTML and add it to the page.

Once an article card is clicked on the home page, the user is redirected to this link: http://localhost:3000/pages/article.html?id={id}. The id of the article on the Strapi app is provided as a query parameter. You will take that id and use it in the request URL to the Strapi app.

The script for the article page, pages/article.html, is found at scripts/article.js. Copy the code below and add it to that file.

    // scripts/article.js
    function checkForArticle() {
        const urlParams = new URLSearchParams(window.location.search);
        const articleId = urlParams.get('id');

        if (articleId) {
            getArticle(articleId);
        } else {
            showMissingArticleMsg("An article can't be retrieved without an ID.");
        }
    }

    function getArticle(id) {
        const articleReq = new Request(`http://localhost:1337/articles/${id}`);

        fetch(articleReq)
            .then(resp => {
                if (resp.ok) {
                    return resp.json();
                } else {
                    throw new Error(resp.statusText);
                }
            })
            .then(displayArticle)
            .catch(showMissingArticleMsg);
    }

    function displayArticle(article) {
        document.getElementById("article-img").src = `http://localhost:1337${article.cover_image.url}`;

        document.getElementById("article-title").innerHTML = article.title;

        document.getElementById("article-description").innerHTML = article.description;

        document.getElementById("published_date").innerHTML = (new Date(article.published_at)).toDateString();

        let articleTags = document.getElementById("article-tags");
        let tag;

        article.tags.forEach(tg => {
            if (tg.name) {
                tag = document.createElement("span")
                tag.classList.add("article-tag");
                tag.innerHTML = tg.name;

                articleTags.appendChild(tag);
            }
        });

        const showdown = window.showdown;
        const converter = new showdown.Converter();
        document.getElementById("article-content").innerHTML = converter.makeHtml(article.content);

        document.getElementById("article-cont").style = "display: flex; display: -webkit-box; display: -ms-flexbox;";
    }

    function showMissingArticleMsg(msg) {
        document.getElementById("not-found").style = "display: flex; display: -webkit-box; display: -ms-flexbox;";
        document.getElementById("err-msg").innerHTML = msg;
    }

    checkForArticle();
Enter fullscreen mode Exit fullscreen mode

This script has four functions. checkForArticle() checks that an id has been provided in the URL. If there is one, it will call getArticle() which fetches the article from Strapi. If not, it calls showMissingArticleMsg() which displays an error message. The screenshot below shows what that looks like. checkForArticle() is called when the file is loaded.

Error displayed on the Article page when no ID is provided

getArticle() makes a request to the Strapi app for an article given its id. It makes a request to the http://localhost:1337/articles/${id} route using the fetch API. If successful, it calls displayArticle()to show the contents of the article on the page. If not, it throws an error and showMissingArticleMsg() will display the error similar to what is shown in the screenshot above.

displayArticle() takes the contents of an article and puts them on the page. It displays the cover image, title, publishing date, tags, and the content. It does this by adding them to existing elements already on the page. The content received from Strapi is in markdown. You will use showdown to convert it to HTML before adding it to the page.

showMissingArticleMsg() displays an error message on the page whenever there is a problem fetching an article. It’s called several times by the other functions.

Here’s a screenshot of what this page looks like when an article is populated.

Editorial Article Page

This page is available at http://localhost:3000/pages/article.html?id={id} where id is the ID of an existing article on the Strapi app.

This is the end. You’ve created and editorial website using Strapi and vanilla JavaScript.

Conclusion

In this tutorial, you installed a Strapi app and generated an article API. You then made a couple of routes of the articles API public and added sample articles to display. Lastly, you cloned a vanilla JavaScript editorial starter app and added scripts to fetch articles from the Strapi app and display them. If you’d like to add more features to the app, maybe consider including authors in the article and making the tags linkable.

Strapi is a great choice for publishing your editorial content. Not only does it support rich text but it provides a user-friendly interface on its admin panel to compose it. It also offers a customizable API that your editorial site can use to consume the content. To learn more about what else Strapi can offer, check out this overview of its features on its website. You can find the source code for this app on Github.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .