How to Build a News Aggregator App using Strapi and Nuxtjs  
Tags: nuxt, strapi, tutorial
Published: 2021-11-25 10:30:18

If you are an avid reader, you might have a News Aggregator app installed on your device. Wouldn't it be awesome to create your own News Aggregator app that you can control and customize according to your needs?

This is what you'll be doing today by creating a News Aggregator app using Strapi and Nuxt.js.

[Strapi](https://strapi.io/) is a headless CMS (Content Management System) based on Node.js and builds APIs. Strapi provides a UI where you can develop your collection types and subsequent APIs to fetch the data from Strapi using REST or GraphQL API. The best thing about Strapi is that it is completely open-source and self-hosted.

[Nuxt.js](https://nuxtjs.org) is a framework for building Vue.js apps that are universal. It means the code written in Nuxt.js can run on both client and server, offering Client Side Rendering and Server Side Rendering simultaneously.

## Goal

This tutorial aims to learn about Strapi and Nuxt.js by building a News Aggregator app with Strapi and Nuxt.js. In this app, you'll:

- Learn to set up Strapi Collection types
- Learn to set up Frontend app using Nuxt.js
- Use CRON jobs to fetch news items automatically
- Add Search capabilities
- Register subscribers

The source code for this project is available on GitHub: [https://github.com/ravgeetdhillon/strapi-nuxtjs-news-app](https://github.com/ravgeetdhillon/strapi-nuxtjs-news-app).

## Setting Up the Environment

Here is what you’ll need to get started.

**Prerequisites**

- Node.js - This tutorial uses Node v14.18.x
- Strapi - This tutorial uses Strapi v3.6.x
- Nuxt.js - This tutorial uses Nuxt.js v2.15.x

The entire source code for this tutorial is available in this [GitHub repository](https://github.com/ravgeetdhillon/strapi-nuxtjs-news-app).

**Setting Up Project**
You'll need a master directory that holds the code for both the frontend (Nuxt.js) and backend (Strapi). Open your terminal, navigate to a path of your choice, and create a project directory by running the following command:

```bash
    mkdir strapi-nuxtjs-news-app
```

In the `strapi-nuxtjs-news-app` directory, you’ll install both Strapi and Nuxt.js projects.


## Setting Up Strapi

In your terminal, execute the following command to create the Strapi project:

```bash
    npx create-strapi-app backend --quickstart
```

This command will create a Strapi project with quickstart settings in the backend directory.
Once the execution completes for the above command, your Strapi project will start on port *1337* and open up [localhost:1337/admin/auth/register-admin](http://localhost:1337/admin/auth/register-admin) in your browser. At this point, set up your **administrative user**:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765233795_localhost_1337_admin_auth_register-admin+1+1.png)


Enter your details and click the **Let's Start** button, and you'll be taken to the Strapi dashboard.

**Creating Feed Sources Collection Type**

Under the **Plugins** header in the left sidebar, click the **Content-Types Builder** tab and then click **Create new collection type** to create a new Strapi collection.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765250631_localhost_1337_admin_.png)


Create a new collection type with *Display name* - **feedsources** and click **Continue** in the modal that appears.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765270453_localhost_1337_admin_plugins_content-type-builder_content-types_plugins__users-permissions.permission_5.png)


Next, create two fields for this collection type:

1. **link** - *Text* field with *Short text* type
2. **enabled** - *Boolean* field

Once you have added all these fields, click the **Finish** button and save your collection type by clicking the **Save** button.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765307662_localhost_1337_admin_plugins_content-type-builder_content-types_plugins__users-permissions.permission_6.png)


**Creating News Items Collection Type**

In the same way, as you created the Feedsources collection type, create a collection type for storing news items.

In the modal that appears, create a new collection type with *Display name* - **newsitems** and click **Continue**.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765327417_localhost_1337_admin_plugins_content-type-builder_content-types_plugins__users-permissions.permission.png)


Next, create the following fields for your collection type:

1. **title** - *Text* field with *Short text* type
2. **preview** - *Text* field with *Short text* type
3. **link** - *Text* field with *Short text* type
4. **creator** - *Text* field with *Short text* type
5. **sponsored** - *Boolean* field

Once you have added all these fields, click the **Finish** button and save your collection type by clicking the **Save** button.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765340229_localhost_1337_admin_plugins_content-manager_collectionType_application__newsitems.newsitems_page1pageSize10_sorttitle_ASC_3.png)


**Creating Subscribers Collection Type**

Finally, you need to create a collection type for registering subscribers.
Create a new collection type with *Display name* - **subscribers** and click **Continue** in the modal that appears.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765364241_localhost_1337_admin_plugins_content-manager_collectionType_application__newsitems.newsitems_page1pageSize10_sorttitle_ASC_4.png)


For the **Subscribers** Collection type, add the following field to your collection type:

1. **email** - *Email* field

Once you have added this field, click the **Finish** button and save your collection type by clicking the **Save** button.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765387985_localhost_1337_admin_plugins_content-type-builder_content-types_application__subscribers.subscribers.png)


At this point, all of your collection types are set up, and the next thing you need to do is add some data to the Feedsources collection type.

You can add RSS feed URLs according to your choice, but to follow along with this tutorial, add the following URLs and enable them as well:

- [https://blog.logrocket.com/feed/](https://blog.logrocket.com/feed/)
- [https://www.freecodecamp.org/news/rss/](https://www.freecodecamp.org/news/rss/)
- [https://www.twilio.com/blog/feed](https://www.twilio.com/blog/feed)
![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765415204_localhost_1337_admin_plugins_content-type-builder_content-types_application__subscribers.subscribers+1.png)


At this point, you have enough data in your Feedsources collection type to fetch the news items from these feeds.

## Automating News Fetching from Feed URLs

To automate the fetching of news items from the feed URLs, you can take advantage of CRON jobs in Strapi, which allows you to run tasks regularly or at a particular time. For this app, it would be better to check for the new news items and then add them to the Newsitems collection type every day at a specific time.

You can use the [RSS-parser](https://www.npmjs.com/package/rss-parser) NPM package to parse the RSS feeds and get the metadata about the items from the blog. To install this package, open your terminal and run the following commands:

```
    cd backend
    npm install rss-parser --save
```

Next, you need to write a script to fetch the news items from the feeds and add them to the `Newsitems` collection type.

In the `config` directory, create a `feedUpdater.js` file and add the following code to it:

```
    'use strict';
    
    const Parser = require('rss-parser');
    
    // 1
    function diffInDays(date1, date2) {
      const difference = Math.floor(date1) - Math.floor(date2);
      return Math.floor(difference / 60 / 60 / 24);
    }
    
    // 2
    async function getNewFeedItemsFrom(feedUrl) {
      const parser = new Parser();
      const rss = await parser.parseURL(feedUrl);
      const todaysDate = new Date().getTime() / 1000;
      return rss.items.filter((item) => {
        const blogPublishedDate = new Date(item.pubDate).getTime() / 1000;
        return diffInDays(todaysDate, blogPublishedDate) === 0;
      });
    }
    
    // 3
    async function getFeedUrls() {
      return await strapi.services.feedsources.find({
        enabled: true,
      });
    }
    
    // 4
    async function getNewFeedItems() {
      let allNewFeedItems = [];
    
      const feeds = await getFeedUrls();
    
      for (let i = 0; i < feeds.length; i++) {
        const { link } = feeds[i];
        const feedItems = await getNewFeedItemsFrom(link);
        allNewFeedItems = [...allNewFeedItems, ...feedItems];
      }
    
      return allNewFeedItems;
    }
    
    // 5
    async function main() {
      const feedItems = await getNewFeedItems();
    
      for (let i = 0; i < feedItems.length; i++) {
        const item = feedItems[i];
    
        const newsItem = {
          title: item.title,
          preview: item.contentSnippet,
          link: item.link,
          creator: item.creator,
          sponsored: false,
        };
    
        await strapi.services.newsitems.create(newsItem);
      }
    }
    
    // 6
    module.exports = {
      main,
    };
```

In the above code:


1. You declare a `diffInDays` function to calculate the number of days between the two given dates. This function is used in the `getNewFeedItemsFrom` function.
2. In the `getNewFeedItemsFrom` function, you parse the feed (`feedUrl`) using the `RSS-parser` NPM package. Then you filter out the feed items that were created in the last 24 hours (`diffInDays === 0`).
3. In the `getFeedUrls` function, you use the Strapi service (`strapi.services`) to get (`find`) all the enabled (`{enabled: true}`) feed URLs from the Feed Sources (`feedsources`) collection type.
4. The `getNewFeedItems` function calls the `getFeedURLs` to get feed URLs then and loops over the feed URLs array (`feeds`) to fetch the new feed items (`feedItems`) using the `getNewFeedItemsFrom` function. Finally, the function returns all the new feed items (`allNewFeedItems`).
5. In the main function, you loop over the `feeditems` array (feedItems) and construct a `newsItem` object used to create a new news item in the NewItems Collection type.
6. At last, the `main` function is exported by the `feedUpdater.js` file

It is a good idea to export all of your tasks from a single file. So, create a `tasks.js` file in the `config` directory and add the following code to it:

```
    'use strict';
    
    async function updateFeed() {
      return await strapi.config.feedUpdater.main();
    }
    
    module.exports = {
      updateFeed,
    };
```

In the `functions` directory, update the `cron.js` file by adding the following code to it:

```
    'use strict';
    
    module.exports = {
      // 1
      '* * * * *': {
        // 2
        task: async () => {
          await strapi.config.tasks.updateFeed();
        },
        // 3
        options: {
          tz: 'Asia/Kolkata',
        },
      },
    };
```

In the above code:


1. You use the CRON syntax (`* * * *` ) to define when the specified `task` needs to be run. In this case, it will run every minute. But this setting is only for testing purposes. Once you have verified that the CRON job works successfully, replace the `* * * * *` with `0 12 * * *`, which makes the CRON job run at 12:00 every day following the time zone specified. For more info about CRON syntax, you can try [https://crontab.guru/](https://crontab.guru/).
2. The `task` key is provided with the `updateFeed` function.
3. The `options.tz` is used to specify the time zone for the CRON job to run in.

Finally, to enable the CRON jobs in Strapi, add the following config settings in the `config/server.js` file:

```
    module.exports = ({ env }) => ({
      ...
      cron: {
        enabled: true,
      },
    })
```

At this point, shut down the Strapi development server by pressing **Control-C** and restart it by running the following command:

```bash
    npm run develop
```

Wait for a minute, and you'll see that the CRON job will execute and update the Newsitems collection type:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765658619_localhost_1337_admin_plugins_content-manager_collectionType_application__newsitems.newsitems_page1pageSize10_sorttitle_ASC.png)


Once you are happy with the result, shut down the Strapi development server and change the replace the `* * * * *` with `0 12 * * *` in the `functions/cron.js` file.

## Setting Up API Permissions

At this point, you have enough data in your Strapi CMS to test the API.
Open [Postman](https://www.postman.com/) and send a *GET* request to the Newsitems API endpoint - [localhost:1337/newsitems](http://localhost:1337/newsitems). You will not be able to access the endpoint as you have not allowed public access to it.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765676725_Screenshot_from_2021-10-27_12-36-16.png)


Since you want to allow public access to your `Newsitems` collection type, so you need to configure the permissions related to the Public role. So to configure the permissions for your news items endpoint, click on the **Settings** tab under the **General** header and select **Roles** under the **Users & Permissions Plugin**. Click the **Edit** icon to the right of the Public Role.


![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765755115_localhost_1337_admin_plugins_content-type-builder_content-types_application__subscribers.subscribers_1.png)


Scroll down to find the **Permissions** tab and check the **find** and **findone** permissions for the Newsitems collection type. For the Subscribers collection type, check the **create** permission to allow users to signup as subscribers. Once done, save the updated permissions by clicking the **Save** button.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765802479_localhost_1337_admin_plugins_content-manager_collectionType_application__newsitems.newsitems_page1pageSize10_sorttitle_ASC_5.png)


Go back to Postman, send a *GET* request to the [localhost:1337/newsitems](http://localhost:1337/newsitems), and you'll get the list of news items from the Strapi.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765824805_Screenshot_from_2021-10-27_17-25-04.png)


Next, send a *GET* request to, for example, [localhost:1337/newsitems/7](http://localhost:1337/newsitems/7), to fetch an individual news item from the Strapi, and you'll get the individual news item with *ID* 7 as a response from the Strapi.

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765832687_Screenshot_from_2021-10-27_17-25-27.png)


That's it for the Strapi part of the project. Next, you need to set up a Nuxt.js app and connect it with the Strapi backend.


## Setting Up Nuxt.js

Now that you have completely set up your Strapi project, it's time to build the Nuxt.js frontend app.

Since your current terminal window is serving the Strapi project, open another terminal window and execute the following command from the project's root directory (`strapi-nuxtjs-news-app`) to create a Nuxt.js project:

```bash
    npx create-nuxt-app frontend
```

On the terminal, you'll be asked some questions about your Nuxt.js project. For this tutorial, choose the options highlighted below:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765853087_Screenshot_from_2021-10-27_12-43-18.png)


Once you have answered all the questions, it will install all the dependencies.
After the installation is complete, navigate into the `frontend` directory and start the Nuxt.js development server by running the following commands:

```
    cd frontend
    npm run dev
```

This will start the development server on port *3000* and take you to [localhost:3000](http://localhost:3000/). The first view of the Nuxt.js website will look like this:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765862481_localhost_3000__1.png)


**Installing @nuxtjs/strapi Module**
[@nuxt/strapi](https://strapi.nuxtjs.org/) is the Nuxt module for integrating Strapi with Nuxt.js.

Shut down the Nuxt.js development server by pressing **Control-C** in your terminal and execute the following command to install the module for your Nuxt.js app:

```bash
    npm install @nuxtjs/strapi --save
```

Once the installation is complete, open the `nuxt.config.js` file and add the following properties to the default object exported by `nuxt.config.js`:

```
    export default {
      ...
    
      // 1
      modules: [
        ...
        '@nuxtjs/strapi',
      ],
    
      // 2
      strapi: {
        url: '<http://localhost:1337>',
        entities: ['newsitems', 'subscribers'],
      },
    }
```

In the above config:


1. You added the `@nuxtjs/strapi` module to the `modules` array so that Nuxt.js loads this package whenever the Nuxt.js app is initialized.
2. You declare the `strapi` config variable. `url` corresponds to the URL of the Strapi server. In the `entities` array, you can specify the collection types present in your API. This will help you to access them using the `$strapi` object, for example - `$strapi.$newsitems`. For more options, you can refer to this [official documentation](https://strapi.nuxtjs.org/options).

**Designing a Layout Page**
Before you start designing the core pages of the app, you can create a **default** layout that contains the styles that are applied to all the pages using the default layout.

At the root of the Nuxt.js project (`frontend`), create a `layouts` directory. Then in the `layouts` directory, create a `default.vue` file and add the following code to it:

```
    <template>
      <Nuxt />
    </template>
    
    <style>
    html,
    body {
      font-family: 'Inter';
    }
    
    .one-liner {
      text-overflow: ellipsis;
      overflow: hidden;
      white-space: nowrap;
    }
    </style>
```

In the above layout, you have defined the **Inter** font for your Nuxt.js app and created a `.one-liner` CSS class which you'll be using later in the core pages to restrict the multiline paragraph to a single line for better UI across the app.


## Designing News Pages

Now that you have set up the necessary packages for developing your Nuxt.js website, you need to design the news pages.

**Designing All News Page**
This page will fetch all of your news items from Strapi CMS and display them in the UI.
In the `pages` directory, open the `index.vue` file and replace all the existing code with the following code:

```
    <template>
      <section class="py-5">
        <b-container>
          <b-row>
            <b-col lg="7">
              <!-- 3 -->
              <div v-if="!newsItems">Loading...</div>
              <!-- 4 -->
              <div v-else>
                <h1 class="mb-5 border-bottom">News</h1>
                <nuxt-link to="/search">Search</nuxt-link>
                <br /><br />
                <div
                  v-for="(newsItem, index) in newsItems"
                  :key="index"
                  class="mb-5"
                >
                  <news-item :item="newsItem"></news-item>
                </div>
              </div>
            </b-col>
          </b-row>
        </b-container>
      </section>
    </template>
    
    <script>
    export default {
      layout: 'default',
      data() {
        return {
          // 1
          newsItems: null,
        };
      },
      // 2
      async created() {
        this.newsItems = await this.$strapi.$newsitems.find();
      },
    };
    </script>
```

In the above code:

1. You set the `newsItems` as `null` in the `data` object which is passed to the `<template>`.
2. In the `created` lifecycle hook, you fetch (`find()`) all the news items (`$newsitems`) from the Strapi (`$strapi`) and assign the response to the `newsItems` data variable.
3. In the `<template>`, you check if the `newsItems` variable is *Falsy*, then you render a `Loading...` message.
4. Once the `newsItems` variable evaluates to a *Truthy*, you loop (`v-for`) over it and render the `news-item` component by passing the current `newsItem` to the `item` prop.

In the `components` directory, create a new file, `NewsItem.vue` and add the following code to it:

```
    <template>
      <div>
        <b-badge v-if="item.sponsored" variant="info" class="mb-2">
          Sponsored
        </b-badge>
        <nuxt-link :to="`/newsitems/${item.id}`" class="text-dark">
          <h2 class="h4">{{ item.title }}</h2>
        </nuxt-link>
        <p
          class="mb-1 one-liner text-muted"
          v-html="sanitizeHtml(item.preview)"
        ></p>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        item: {
          type: Object,
          default: () => ({}),
        },
      },
    };
    </script>
```

Since you will parse the HTML from external sources, it makes your app vulnerable to [XSS attacks](https://owasp.org/www-community/attacks/xss/). You first need to sanitize the HTML and then pass it to the `v-HTML` prop to mitigate this problem.

You can use the [DOMPurify](https://www.npmjs.com/package/dompurify) library to sanitize the HTML and prevent XSS attacks. In your terminal, run the following command to install this package:

```bash
    npm install dompurify --save
```

Sanitization is used across various places in an app. So to respect the **DRY** (Don't Repeat Yourself) principle, it is often a good idea to create [mixins](https://vuejs.org/v2/guide/mixins.html) to make these functions available across your app without having to write the same code again and again.

In the `plugins` directory, create an `index.js` file and add the following code to it:

```
    import Vue from 'vue';
    import DOMPurify from 'dompurify';
    
    Vue.mixin({
      methods: {
        sanitizeHtml(value) {
          return DOMPurify.sanitize(value);
        },
      },
    });
```

Add the above plugin to the `plugins` array in the `nuxt.config.js` file as it allows the Nuxt.js to execute these [plugins](https://go.nuxtjs.dev/config-plugins) before rendering a page as in the code below:

```
    export default {
      ...
      plugins: ['~/plugins/index.js'],
    };
```

At this point, save your progress and start your Nuxt.js development server by running:

```bash
    npm run dev
```

Visit [localhost:3000](http://localhost:3000/articles) and you’ll see your news page rendered by Nuxt.js:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765915068_localhost_3000__2.png)


**Designing a Single News Item Page**
The next step is to design a single news item page that needs to be [dynamic](https://nuxtjs.org/docs/features/file-system-routing/). You can fetch a single news item from endpoint - [localhost:1337/newsitems/:id](http://localhost:1337/newsitems/:id).

In the `pages` directory, create a sub-directory, `newsitems`. Then in the `newsitems` directory, create a `_id.vue` file and add the following code to it:

```
    <template>
      <section class="py-5">
        <b-container>
          <b-row>
            <b-col lg="7" class="mx-lg-auto">
              <!-- 3 -->
              <div v-if="!newsItem">Loading...</div>
              <!-- 4 -->
              <div v-else>
                <nuxt-link to="/">Back</nuxt-link>
                <br /><br />
                <b-alert v-if="newsItem.sponsored" variant="info" show>
                  This is a Sponsored post.
                </b-alert>
                <h1 class="mb-4">{{ newsItem.title }}</h1>
                <div class="small mb-4">
                  <span v-if="newsItem.creator.trim().length > 0">
                    Written by <b>{{ newsItem.creator }}</b>
                    <br />
                  </span>
                  <span>
                    Published on
                    {{ new Date(newsItem.published_at).toLocaleDateString() }}
                  </span>
                </div>
                <p v-html="sanitizeHtml(newsItem.preview)"></p>
                <a :href="newsItem.link" target="_blank">
                  Read on Original Blog
                  <!-- 5 -->
                  <ExternalIcon />
                </a>
              </div>
            </b-col>
          </b-row>
        </b-container>
      </section>
    </template>
    
    <script>
    export default {
      layout: 'default',
      data() {
        return {
          // 1
          newsItem: null,
        };
      },
      // 2
      async created() {
        const { id } = this.$route.params;
        this.newsItem = await this.$strapi.$newsitems.findOne(id);
      },
    };
    </script>
```

In the above code:

1. You set the `newsItem` as `null` in the `data` object which is passed to the `<template>`.
2. In the `created` lifecycle hook, first, you destructure the `id` of the dynamic route from `this.$route.params` object. Then, you use the `$strapi` object to fetch (`findOne()`) the news item with id (`id`) and assign the response to the `newsItem` data variable.
3. In the `<template>`, you check if the `newsItem` variable is *Falsy*, then you render a `Loading...` message.
4. Once the `newsItem` variable evaluates to a *Truthy*, you use Vue template variables to render the UI for it.
5. You can see that you have referenced an `<ExternalIcon />` component so next, you need to create one.

In the `components` directory, create an `ExternalIcon.vue` file and add the following code to it:

```
    <template>
      <svg width="12px" height="12px" viewBox="0 0 24 24">
        <g
          stroke-width="2.1"
          stroke="currentColor"
          fill="none"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <polyline points="17 13.5 17 19.5 5 19.5 5 7.5 11 7.5"></polyline>
          <path d="M14,4.5 L20,4.5 L20,10.5 M20,4.5 L11,13.5"></path>
        </g>
      </svg>
    </template>
```

Save your progress and wait for the server to Hot Reload. Click on any news item on the index page to open the single news item page and the page will render as follows:


![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765930118_localhost_3000__3.png)

## Adding Search Functionality

Now that you have created a podcasts page, the next step is to design a single podcast page that needs to be [dynamic](https://nuxtjs.org/docs/features/file-system-routing/) and allow the user to listen to the podcast. You can fetch your podcast from endpoint - [localhost:1337/podcasts/:id](http://localhost:1337/podcasts/:id).

In the `pages` directory, create a `search.vue` file and add the following code to it:

```
    <template>
      <section class="py-5">
        <b-container>
          <b-row>
            <b-col lg="7" class="mx-lg-auto">
              <nuxt-link to="/">Back</nuxt-link>
              <br /><br />
              <h1 class="mb-5 border-bottom">Search News</h1>
              <div class="d-flex mb-5">
                <!-- 3 -->
                <b-form-input
                  v-model="searchQuery"
                  type="search"
                  placeholder="Search"
                  class="mr-3"
                ></b-form-input>
                <!-- 4 -->
                <b-btn @click="searchItems">Search</b-btn>
              </div>
              <div v-if="!newsItems">Nothing Found</div>
              <div
                v-for="(newsItem, index) in newsItems"
                v-else
                :key="index"
                class="mb-5"
              >
                <news-item :item="newsItem"></news-item>
              </div>
            </b-col>
          </b-row>
        </b-container>
      </section>
    </template>
    
    <script>
    export default {
      layout: 'default',
      // 1
      data() {
        return {
          newsItems: null,
          searchQuery: null,
        };
      },
      // 2
      methods: {
        async searchItems() {
          this.newsItems = await this.$strapi.$newsitems.find({
            _q: this.searchQuery,
          });
        },
      },
    };
    </script>
```

In the above code:


1. You set the `newsItems` and `searchQuery` as `null` in the `data` object which is passed to the `<template>`.
2. You declare the `searchItems()` method which is used to search the NewsItems (`$strapi.$newsitems`) collection type by providing the `searchQuery` data variable as the query parameter (`_q`).
3. The text field (`b-form-input`) is bound to the `searchQuery` data variable using the `v-model`.
4. You have added a button (`b-btn`) that runs the `searchItems` function on button click (`@click`).

Save your progress and wait for the server to Hot Reload. Go to the [localhost:3000/search](http://localhost:3000/search) and try searching for a news item:


![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765942946_localhost_3000__5.png)

## Registering Subscribers

The final step to complete your News Aggregator app is to allow users to signup as subscribers.
In the `components` directory, create a `SubscribeBox.vue` file and add the following code to it:

```
    <template>
      <div class="bg-light">
        <div class="p-3">
          <p class="lead font-weight-normal mb-0">Subscribe to our newsletter</p>
          <p class="text-muted">
            Get daily updates about things happening in the world of tech and
            business.
          </p>
          <div class="d-flex flex-column">
            <!-- 3 -->
            <b-form-input
              v-model="email"
              type="email"
              placeholder="Your Email"
              class="mb-2"
            ></b-form-input>
            <!-- 4 -->
            <b-btn @click="addSubscriber">Subscribe</b-btn>
            <!-- 5 -->
            <p v-if="message" class="mt-3 mb-0">{{ message }}</p>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      // 1
      data() {
        return {
          email: null,
          message: null,
        };
      },
      // 2
      methods: {
        async addSubscriber() {
          const response = await this.$strapi.$subscribers.create({
            email: this.email,
          });
          if (response) {
            this.message = 'Thanks for subscribing!';
          }
        },
      },
    };
    </script>
```

In the above code:

1. You set the `email` and `message` as `null` in the `data` object which is passed to the `<template>`.
2. You declare the `addSubscriber` method which is used to create (`create({email: this.email})`) a new subscriber in Subscribers Collection type (`$strapi.$subscribers`).
3. The `email` data variable is bound to the Form Text input (`b-form-input`) using the `v-model`.
4. You have added a button (`b-btn`) that runs the `searchItems` function on the button click (`@click`).
5. If the `message` evaluates to a *Truthy*, you render the message telling the user that they have been successfully subscribed.

At this moment `SubscribeBox` is just a component, so you need to render it in a page. Open the `pages/index.vue` file and update the `<template>` by adding the following code:

```
    <template>
      <section class="py-5">
        <b-container>
          <b-row>
            <b-col lg="7">
              <!--  -->
            </b-col>
            <b-col lg="1"></b-col>
            <b-col lg="4">
              <SubscribeBox />
            </b-col>
          </b-row>
        </b-container>
      </section>
    </template>
```

Save your progress and wait for the server to Hot Reload. Go to the [localhost:3000](http://localhost:3000) and the `SubscribeBox` component will render as follows:


![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1635765957268_localhost_3000__1+1.png)


Test the Subscription form by adding a valid email and click **Subscribe** button. Once you get a thanks message, check out the Subscribers collection type in your Strapi CMS for the newly registered email:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1637648192593_Screencast+from+27-10-21+05_16_42+PM+IST+online-video-cutter.com+2+1.gif)


That's it. Your app is complete and below is a complete overview of the app in action:

![](https://paper-attachments.dropbox.com/s_979253D2F6E1AA6BFF58D4532B5A88A87639C2D0A24237B3B5DD60D063B1C399_1637648508253_Screencast+from+27-10-21+05_16_42+PM+IST+online-video-cutter.com+1+1.gif)

## Conclusion

That's it! You have successfully set up a News Aggregator app using Nuxt.js as a frontend and Strapi as a backend. You learned about API Permissions in Strapi, CRON jobs in Strapi, implementing views in Nuxt.js, and more. The next step would be to deploy the app. You can [deploy the Strapi CMS on DigitalOcean](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/digitalocean.html) and the [Nuxt.js app on Netlify](https://nuxtjs.org/deployments/netlify/).

The entire source code for this tutorial is available in this [GitHub repository](https://github.com/ravgeetdhillon/strapi-nuxtjs-news-app).