Build an Editorial Website with vanilla JavaScript and Strapi
Tags: javascript, jamstack, cms, strapi
Published: 2021-11-22 16:10:13

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](https://nodejs.org/en/download/releases/). 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:

```bash
    npm i strapi -g
```

3. Git installed. You will need this to clone the editorial app starter. [This guide explains how to install it on your specific operating system.](https://github.com/git-guides/install-git) 
4. Basic knowledge of JavaScript and Markdown. You can [learn more about JavaScript from this basics tutorial](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics) and [Markdown from this extensive guide](https://guides.github.com/features/mastering-markdown/). 

## 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:

```bash
    strapi new editorial-strapi-app --quickstart
```

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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635604352221_strapi-signup.png)


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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635604813559_strapi-admin-portal.png)


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
```

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
        }
      }
    }
```

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
```

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
        }
      }
    }
```

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:

```bash
    npm run develop
```

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. 
    

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](http://localhost:1337/admin).
2. [Follow this link to the](http://localhost:1337/admin/settings/users-permissions/roles/2) [**Public Roles**](http://localhost:1337/admin/settings/users-permissions/roles/2) [settings](http://localhost:1337/admin/settings/users-permissions/roles/2). 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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635633227868_strapi-permissions.png)


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**](http://localhost:1337/admin/plugins/content-manager/collectionType/application::article.article/create) [article form](http://localhost:1337/admin/plugins/content-manager/collectionType/application::article.article/create). 
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](https://jaspervdj.be/lorem-markdownum/) 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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635638233426_strapi-create-article.png)


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
```

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

```
    cd editorial-app
```
 
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
```

Next, install the app’s dependencies. 

```bash
    npm install
```

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:

```bash
    npm run dev
```

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();
```

`fetchArticles()` makes a request to http://localhost:1337 using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635653742829_editorial-home.png)


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();
```


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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635655879775_editorial-no-id.png)


`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](https://paper-attachments.dropbox.com/s_34217FCBC1F7CC5AE22F3B5BDAAB3FD613C05682A4378755DE04C39BB031DF7E_1635656679909_editorial-post.png)


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](https://strapi.io/features). [You can find the source code for this app on Github.](https://github.com/zaracooper/editorial-app)