Create a Newsletter app with Twitter Revue, Next.js API Routes, and Tailwindcss
Tags: nextjs, tailwindcss, twitter, sideprojects
Published: 2021-08-28 06:15:10

Hey there 👋, do you like to learn from video tutorials? This article is also available as video content.

{% youtube XbtgjxWFssQ %}

*Please feel free to [subscribe](https://www.youtube.com/tapasadhikary) for the future content*

<hr />

Do you have an email newsletter, or consider starting one? An email newsletter gives your subscribers regular updates about your work, products, passion, life, journey, anything that you find suitable to share. We have some great vendors/products/sites that help us to instantly get started with an email newsletter service. Buttondown, Mailchimp, MailerLite, Substack are just a few to name here.

Early this year, [Twitter announced](https://blog.twitter.com/en_us/topics/company/2021/making-twitter-a-better-home-for-writers) the acquisition of [Revue](https://www.getrevue.co/), a service that makes it free and easy for anyone to start and publish editorial newsletters. Not only that. Twitter has also made Revue’s Pro features free for all accounts.

A few days back, a tweet from Revue's official account confirmed that they would allow people to subscribe to your Revue newsletter directly from your Twitter profile. Keeping some debates aside, I think it is a great move.

{% twitter 1428371221524189186 %}

As the owner of a newsletter, we can promote it in many ways. 
- We can link to the [newsletter page](https://www.getrevue.co/profile/tapasadhikary) from our website, blog. 
- We can embed the subscription form to our website using simple JavaScript, HTML, CSS snippets provided by vendors. 
- Lastly, if the newsletter vendor provides APIs to access data, we can create, manage the newsletter entirely within our control. It is a powerful usage that gives your users a feeling of `oneness` being part of the same website, similar look-and-feel.

# So, What's the Plan?
This tutorial will teach how to use the `Revue` APIs to fetch data into a `Next.js` application using the API routes(serverless functions). We will also use the `tailwindcss` to give the app a better look and feel. 

I am on my way to migrate [my website](https://tapasadhikary.com/) using `Next.js` and `tailwindcss`, and the newsletter will be a part of it. So, it is an excellent opportunity to share what I have implemented and learned.

# TL;DR
If you want to jump into the final app or the source code early, here are the links,

- [The Newsletter App Link](https://next-starter-revue-tailwind.vercel.app/)
- [The Entire Source Code on the GitHub](https://github.com/atapas/next-starter-revue-tailwind)
- [(Again) The YouTube Link](https://www.youtube.com/watch?v=XbtgjxWFssQ)

# Setup a Newsletter Service using Revue
To set up a newsletter with `Revue`, sign up to [https://www.getrevue.co/](https://www.getrevue.co/) using your Twitter account or email. 

![Revue Sign up](https://cdn.hashnode.com/res/hashnode/image/upload/v1629961288039/a9cLtdxcW.png?border=1,CCCCCC&auto=compress)

Next, log in to your account to set up the newsletter by providing the name, description, layout, issues, and schedule. You can [integrate many services](https://www.getrevue.co/app/integrations) like Twitter, Facebook, Instagram with your Revue account to fetch content from them to add to the newsletter. Additionally, you can fetch the data using the RSS feeds. You can integrate your Hshnode or Dev blog's RSS feed as well. I've made my wish already 😆!

{% twitter 1428744252532477955 %}

The bottom of the [integration page](https://www.getrevue.co/app/integrations) shows your API key to access the newsletter data over HTTP requests. Please copy this key and keep it safe.

![API Key](https://cdn.hashnode.com/res/hashnode/image/upload/v1629961839803/kYd5WYoA7.png?border=1,CCCCCC&auto=compress)

This API key will be part of the `Authorization` header value when using the Revue APIs. [Here is the link](https://www.getrevue.co/api#get-/v2/lists) to learn about all publicly available APIs. In this tutorial, we will use the following,

- `POST /v2/subscribers`: Add a subscriber to the list.
- `GET /v2/subscribers`: Returns a list of your active subscribers.
- `GET /v2/issues`: Returns a list of your sent issues.

But, before that, let us build the user interface of the Newsletter Subscription app.

# Build a Newsletter Subscription App using Next.js and Tailwindcss
There are plenty of starter projects available in GitHub to get started with Next.js and Tailwindcss. My personal favorite is [next-starter-tailwind](https://github.com/taylorbryant/next-starter-tailwind) because of its simplicity. I'll be using it as a template to create a repository for the newsletter subscription app. Please feel free to use any other starter project you are comfortable with.

Please create a repo by clicking on the `Use this template` button of the `next-starter-tailwind` repository.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629963836334/CGyvzhFNu.png?border=1,CCCCCC&auto=compress)

Provide required details and create a repository from the template.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629963928403/7fMP3ykgS.png?border=1,CCCCCC&auto=compress)

Now clone the repository and browse to the project folder. Open a command prompt or terminal window to install dependencies using the following command,

```shell
npm install # Or, yarn install
```
At this stage, please open the project with your favorite code editor(VS Code, in my case) and make minor code changes. Open the `header.js` file under the `components` folder and find the `Next.js Starter Tailwind` text. Change this text to `Newsletter demo powered by Next.js Revue Tailwind`. Additionally, you can change the creator name, GitHub information in the `footer.js` file.

Now save your changes and use this command from your command prompt to launch the app. 

```shell
npm run dev # Or, yarn dev
```

Access the app using the URL `http://localhost:3000`. You should see the initial user interface coming up.

![Initial UI](https://cdn.hashnode.com/res/hashnode/image/upload/v1629964964534/ZAjQJYCfE.png?border=1,CCCCCC&auto=compress)

## Create the Subscription Form
Let's create a basic subscription form with a single email field and a button to subscribe. Please create a new file called `Subscribe.js` under the `components` folder with the following content.

```js
const Subscribe = () => {
  return (
    <div className="border border-gray-200 rounded p-6 my-4 w-full bg-gray-50">
      <p className="text-gray-900 mb-6 text-lg md:text-xl">
         Want to keep your brain engaged with great UI/UX learning content?
      </p>
      <p className="text-gray-800 dark:text-gray-400 mb-10 text-base">
        Enter your email address and you'll be be added to my email newsletter, of which you can opt out any time.
      </p>
      <form className="relative my-4">
        <input
          aria-label="Email for newsletter"
          placeholder="john@email.com"
          type="email"
          autoComplete="email"
          required
          className="py-4 px-0 text-md bg-transparent w-9/12 text-gray-900 border-b-2 border-gray-600 dark:border-gray-400 dark:text-white focus:border-brand focus-visible:outline-none"
        />
        <button
          className="flex justify-center px-5 py-4 mt-8 bg-green-600 text-white font-bold text-lg"
          type="submit"
        >
          Subscribe
        </button>
      </form>

      <p className="text-xl text-gray-800 dark:text-gray-200">
        14 subscribers . 3 issues
      </p>
    </div>
  );
};

export default Subscribe;
```
It is a react component with a simple form having one email field and a button. We have also hardcoded the subscribers and issues count. Later, we will make the API calls to fetch those. We have styled the HTML element using [tailwindcss classes](https://tailwindcss.com/docs).

Now move over to the `index.js` under the `pages` folder. Replace the content of the file with the following,

```js
import Subscribe from "@components/Subscribe";

export default function IndexPage() {
  return (
    <Subscribe />
  );
}
```
Here we are importing and using the `Subscribe` component so that when the app loads, it shows the newsletter subscription form. Let's refresh the page. You should see subscription forms like,

![Subscription Form](https://cdn.hashnode.com/res/hashnode/image/upload/v1630071552191/Kp8AMgDwD.png?border=1,CCCCCC&auto=compress)

# Create Next.js API Routes to Subscribe, and Many More
Now it's time to create `Next.js API Routes` to register a new subscriber, get the subscriber count, and list of issues.

## Next.js Serverless Functions
With Next.js's [API Routes](https://nextjs.org/docs/api-routes/introduction), you can easily create API endpoints. In the background, it uses Node.js serverless functions. You need to create these functions inside the `pages/api` folder. So, let us first create a folder called `api` under the `pages` folder. 

We will need the Revue API key now. Please create `.env.local` file at the root of the project folder with the following line,

```js
REVUE_API_KEY=<REPLACE_THIS_WITH_REVUE_API_KEY>
```
Please use your `API Key` you have copied from the revue integration page earlier.

At this stage, you need to restart the local server for the environment variable to get loaded in our app. So stop the server and restart it using the `yarn dev` command.

Let's create the API route to register a new subscriber.

## But, Hold On! Why Can't We use the Revue API Directly?
You can. It is possible to use the Revue APIs directly in your React components. However, there are a few advantages of using it via the Next.js APIs.
- In the future, if you want to use another newsletter service other than Revue, your user interface component code never changes. You just change the serverless function and redeploy.
- There is an abstraction. It helps you to deploy and host just the API separately along with your own business use cases. Alright, let's move on.
- Accessing these APIs directly on the client-side will leave you with the risk of the API key exposed that anyone can obtain easily by inspecting network requests. You do not want that!

## Create Next.js API Route to Register a New Subscriber
Create a file called `subscribe.js` inside `pages/api` folder. It means our API route will be accessible from the UI components using the URI `/api/subscribe`. Please paste the following content in the `subscribe.js` file.

```js
export default async function handler(req, res) {
    // 1. Get the email from the payload and
    // validate if it is empty.
    const { email } = req.body;
    if (!email) {
        return res.status(400).json({error: 'Please provide an email id.'});
    }

    // 2. Use the Revue API Key and create a subscriber using
    // the email we pass to the API. Please note, we pass the
    // API Key in the 'Authorization' header.
    try {
        const API_KEY = process.env.REVUE_API_KEY;
        const response = await fetch(
            `https://www.getrevue.co/api/v2/subscribers`,
            {
                method: 'POST',
                body: JSON.stringify({email: email, double_opt_in: false}),
                headers: {
                    'Authorization': `Token ${API_KEY}`,
                    'Content-Type': 'application/json'
                }
            }
        )
    
    // 3. We check in the response if the status is 400
    // If so, consider it as error and return. Otherwise a 201
    // for create        
        if (response.status >=400) {
            const message = await response.json();
            console.log(message.error.email[0]);
            return res.status(400).json({error: message.error.email[0]});
        }
        // Send a JSON response
        res.status(201).json({
            message: `Hey, ${email}, Please check your email and verify it. Can't wait to get you boarded.`,
            error: ''
        });
    } catch (err) {
    // 4. If the control goes inside the catch block
    // let us consider it as a server error(500)  
        return res.status(500).json({error: err.message || error.toString()});
    }
}
```
A few things are going on in the above function.
1. When someone invokes this API function, we expect an email part of the payload. So first, get the email from the payload and validate if it is empty.
1. Next, use the email and API_KEY to call the Revue API to register a subscriber. Note the payload here. We are passing the email value and `double_opt_in` value as `false`. In reality, you will NOT pass the double_opt_in value as false as you want your subscribers to verify email before confirming. We are doing it just for the demo's sake.
1. Then, we check in the `response` if the status is 400. If so, consider it as an error and return with an error message. Otherwise, a 201 for creating and return with a success message.
1. Last, If the control goes inside the catch block, let us consider it a server error(500).

### Update the UI Code to Register Subscribers
We will update the `Subscribe` component to use the `/api/subscribe` API. Open the `Subscribe.js` file under the `components` folder and make these changes.

1. Import the `useState` hook from `react` to manage a few states. Add this line at the top of the file.

 ```js
  import { useState } from 'react';
 ```
1. Create three state variables to handle the email from the user input and the error, success message from the API call. Add these three lines at the beginning of the `Subscribe` function as,

 ```js
 const Subscribe = () => {
    const [email, setEmail] = useState('');
    const [error, setError] = useState('');
    const [success, setSuccess] = useState('');
      return (
        ..... 
        {/* Rest of the code as is */}
        ....
      )
 }
 ```
1. Next, handle two events. One is to capture the user input in the email field, and the second is to handle the for submit.

 ```html
 ...
 ...
 <form className="relative my-4" onSubmit={subscribeMe}>
        <input
          onChange={changeEmail}
 ``` 
1. Now is the time to define both `subscribeMe` and `changeEmail` methods.

 ```js
 const subscribeMe = async (event) => {
     event.preventDefault();
    
     const res = await fetch("/api/subscribe", {
        body: JSON.stringify({ email: email }),
        headers: { 'Content-Type': 'application/json' },
        method: "POST",
     });

    const { error, message } = await res.json();
     if (error) {
        setError(error);
     } else {
        setSuccess(message);
     }
  };

  const changeEmail = (event) => {
    const email = event.target.value;
    setEmail(email);
  }
 ```
In the `subscribeMe` method, we call the API `/api/subscribe`, passing the email value as the payload. Then we handle the error and success message.
1. Last, let's show the success and error message in the UI. Add this code right after the form element.

 ```html
   {success 
          ? 
       <span className="flex items-center text-sm font-bold text-green-700"> 
            {success}
       </span> 
          : 
       <span className="flex items-center text-sm font-bold text-red-800">
             {error} 
       </span>
   }
```
Great, now go to the app and provide an email id to register. As we have turned off the email verification, you can test it with an arbitrary email id. Please take a look into the entire source file [from here](https://github.com/atapas/next-starter-revue-tailwind/blob/master/components/Subscribe.js).

![Register Success](https://cdn.hashnode.com/res/hashnode/image/upload/v1630082649555/YLQNTgmsf.png?border=1,CCCCCC&auto=compress)

To verify, the email address got added successfully, got to the [subscribers](https://www.getrevue.co/app/lists) page of your account. You should see this new email id added,

![Subascriber added](https://cdn.hashnode.com/res/hashnode/image/upload/v1629973057192/-Webt8GAm.png?border=1,CCCCCC&auto=compress)

> Please make sure to use the email verification by turning on `double_opt_in: true` in the API function for production usage.

Try the same email id again to attempt to register!

![register fail](https://cdn.hashnode.com/res/hashnode/image/upload/v1630082807675/muZ1jY72r.png?border=1,CCCCCC&auto=compress)

Yep, you will get that error. That's all. The subscription works well.

## Get the Subscriber Count
Alright, let's get the subscriber count. So we will now write a serverless function to fetch the subscriber count. Please create a file called `subscribers.js` under the `pages/api` folder with the following content.

```js
export default async function handler(_, res) {
  const API_KEY = process.env.REVUE_API_KEY;
  const response = await fetch('https://www.getrevue.co/api/v2/subscribers', {
    headers: {
      Authorization: `Token ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    method: 'GET'
  });

  const data = await response.json();
  const count = data.length;

  res.setHeader(
    'Cache-Control',
    'public, s-maxage=1200, stale-while-revalidate=600'
  );

  return res.status(200).json({ count });
}
```
We use the Revue API to fetch the subscriber list and then return the count as a response. So, now we have to use the `/api/subscribers` URI to fetch the count. Let's do it.

### Update the UI Code to Fetch Subscriber Count
We need to fetch the subscriber count when the `Subscribe` component loads. Also, if there is a new subscriber, we need to show the updated count in the UI. `Next.js` supports two kinds of `pre-rendering`,

- `Static Generation(SSG)`: In this case, everything is precompiled, prebuilt and cached. You do not see changes in your content until there is another build. It works best when you deal with static data like blog articles.
- `Server-Side Rendering(SSR)`: Here, the data for a page generates on demand for each request.

We prefer static generation as much as possible but may not avoid the server-side rendering in some cases. For our app, we will use `SWR`. As described [here](https://swr.vercel.app/),

> `SWR` is derived from `stale-while-revalidate` , a HTTP cache invalidation strategy popularized by `HTTP RFC 5861`. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

With Next.js `pre-rendering` support and `SWR`, you can pre-render the page for SEO and allow caching, revalidation, and re-fetching at intervals on the client side.

1. Install `swr` library using the command,

 ```js
 npm install swr #Or, yarn add swr
 ```
1. The `swr` library gives us a hook called `useSWR`. It takes two parameters, a `key`  and a fetcher function. The `key` is a string value, usually the API URL that we will pass to the `fetcher` function, and the `fetcher` function can be an asynchronous function. So, let us create a simple fetcher function. 

 Please create a `utils` folder at the root of the project and create a `fetcher.js` file with the following content,

 ```js
  export default async function Fetcher(...args) {
    const res = await fetch(...args);

    return res.json();
  }
 ```
 Next, in the `components/Subscribe.js` file, include these two imports. 

 ```js
 import useSWR from 'swr';
 import fetcher from '../utils/fetcher';
 ```
 Now we can use the `useSWR` hook to pass the API(`api/subscribers`) and the fetcher function.

 ```js
  const Subscribe = () => {

     const [email, setEmail] = useState('');
     const [error, setError] = useState('');
     const [success, setSuccess] = useState('');
     // --- above is old code ---

     const { data } = useSWR('/api/subscribers', fetcher);
     const subscriberCount = data?.count;
 ```
 Please note, we use the `JavaScript optional chaining(?.)` feature to get the count value. It handles the `undefined` value much safely.

 Every time the data gets changed at the back-end, the `subscriberCount` variable will have the latest count.

1. Last is to use the `subscriberCount` state variable instead of the hardcoded value.

 ```html
  <p className="text-sm text-gray-800 dark:text-gray-200">
     { subscriberCount } subscribers . 3 issues
  </p>
 ```
That's all. Refresh the app and see the actual count reflecting.

![Subscriber count](https://cdn.hashnode.com/res/hashnode/image/upload/v1630083167179/SbLVFz4lo.png?border=1,CCCCCC&auto=compress)

## Get the Issue List
Now we need to get the issue list and the count of published issues. So we have to write a serverless function again to fetch these details. But wait, I'm not going to do that in this tutorial. Please take it as an exercise to try out. 

Hint: You need to use this Revue API to fetch the data => `GET /v2/issues`. If you need help, the [API code is here](https://github.com/atapas/next-starter-revue-tailwind/blob/master/pages/api/issues.js), and the [component changes](https://github.com/atapas/next-starter-revue-tailwind/blob/master/components/Subscribe.js) are here to refer to.

In the end, the UI should have the actual issue count and a list of published issues like this(I have one test issue).

![final page](https://cdn.hashnode.com/res/hashnode/image/upload/v1630083249642/_OUZULeLh.png?border=1,CCCCCC&auto=compress)

# Let's Deploy
Congratulations!!! The app is ready to use. But, it is available only with you. Let's deploy it publicly. We will use the [Vercel](https://vercel.com/) platform to deploy our app. It is super easy to deploy a Next.js app on Vercel using a few simple steps. To make it happen, please commit and push all your code changes to the `GitHub` repository.

1. Create an account with Vercel, log in and click on the `New Project` button to get started.

 ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629979553563/T5xJig9eN.png?border=1,CCCCCC&auto=compress)

1. Next, import your project from GitHub.

 ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629979633056/Lwb0k4SFB.png?border=1,CCCCCC&auto=compress)

1. Now, you need to configure your project. For a Next.js project, you hardly need to make any changes to the build and other parameters. If your app is depending on any Environment Variables, you need to add them one by one. In our case, we have one. So let's add it. Then, click on the `Deploy` button.

 ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629979696642/0buvXxOsn.png?border=1,CCCCCC&auto=compress)

1. Congratulations!!! You have deployed the app successfully on Vercel. Now you can access the app publicly using the URL generated by the deployment process.

 ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629979809143/GeERj2HL8.png?border=1,CCCCCC&auto=compress)

Post-deployment, you can perform many checks and additional configurations based on your needs. If your app has one or more serverless functions, you can see the live execution logs from your project's `Functions` tab. The image below shows the log for our functions.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629982080277/tl8GRrAec.png?border=1,CCCCCC&auto=compress)

# In Summary
- `Next.js` is the future(arguably?) for React-based projects. It is easy to set up, learn, and use. The `tailwindcss` is a developer-friendly CSS library to style the app. `Revue` is an amazing newsletter service.
- Your users, customers like the `oneness` and the `belongingness`. Then why not get the newsletter service within the app/site itself and manage it?
- `Next.js APIs` are the Node.js `serverless functions` in the background. It is a great way to fetch, interact with back-end services.
- It is super easy to deploy and maintain your Next.js app(including serverless functions) using `Vercel`.
- Similar to this, you can integrate many other services like GitHub, your blog, and many more that I'll cover in my upcoming post.

<hr/>

That's all. I hope you enjoyed building it with me. Please share/like this article and the video tutorial so that it reaches others as well. 

Let's connect. Please find me on [Twitter(@tapasadhikary)](https://twitter.com/tapasadhikary), sharing thoughts, tips, and code practices. Would you please give a follow?