Create a serverless API with Next.js and Mailchimp

by Kartik Chaturvedi • May 13, 2020

Post image

This tutorial is the first in a two-part series on custom forms with server-side APIs and third-party services.

Part One - Backend API

Part Two - Frontend form

The future is API-driven. We have APIs for weather, payments, travel, and even sports. RESTful architecture and API frameworks are what make ordinary apps and websites into powerful tools in today's connected world.

Using these frameworks, we can create tailored experiences for users, without having to reinvent the systems that power those experiences. In this tutorial, we will use Next.js, a fantastic React-based web development framework, to create a beautiful frontend form and some server-side middleware. This application will send an email address for a new subscriber to a mailing list on Mailchimp.

This tutorial assumes you are familiar with React, Next.js, and RESTful architecture.

Initial Setup

Setting up Next.js is super simple:

npm init next-app

Next.js will set up a default project and install all dependencies for you. Once installed, inside of the pages/ directory, you will find the default Next.js index.js welcome page. If you start the development server and make changes to this file, you will see the changes updated live in your browser.

Create the API

In this tutorial, we’ll be using Mailchimp’s API to add a new email address as a contact in a campaign mailing list.

To create an API, create a folder named api/ in the pages/ directory. Next.js will take any file inside the api/ folder and create an API instead of a page. Here, create a new file named subscribe.js. This API will be accessible from the web at your-site.com/api/subscribe.

Next.js provides a clean framework to handle the request and response in the API. All we need to do here is take the email address from the request and send it to Mailchimp’s API. Let’s start by exporting a default function in subscribe.js that returns a JSON object with one key-value pair:

export default async (req, res) => {
  res.end(JSON.stringify({ response: 'hello world' }))
}

The async keyword is important, as we will be using the companion await keyword to make asynchronous calls to Mailchimp.

You can visit the API endpoint in the browser or using a tool like Postman and see the response we coded in the last step.

{
  "response": "hello world"
}

In Next.js, req and res are default parameters sent and expected by the framework. req is an object that contains all of the request data — headers, cookies, query values, and of course, the request body. We should only need the body at this point, accessible via the req.body object. Let's expect a field called emailAddress in the request, which will contain the new email address for the new subscriber. We'll pull that into a variable called email for later.

export default async (req, res) => {
  const email = req.body.emailAddress
}

To call Mailchimp's API that will add an email address to your mailing list, first create an API within your Mailchimp account. Using this key, you will authenticate your requests to Mailchimp, similar to a password. This will be stored and executed from the our subscribe API, so no visitor to your website will ever see it.

To call APIs, we will need to use an HTTP client like fetch, which comes bundled with Next.js.

The fetch API is simple to use. We just need to call fetch() with Mailchimp’s URL and an object containing the required parameters. Since fetch() returns a Promise, we will use await to resolve it. Since both the Promise and the fetch call could fail, we wrap the call in a try-catch block.

export default async (req, res) => {
  const email = req.body.emailAddress
  
  try {
    const response = await fetch({
      // parameters will go here
    })
  } catch { }
}

The Mailchimp API documentation defines the endpoint and calls for two fields to be sent in the request: the email_address, for which we will pass the email value extracted from req.body earlier, and the status for that email address, for which we will send subscribed. The fetch documentation shows that the first parameter in the fetch() call is the URL, and the second parameter is an object with additional fields. The body field is where we will pass the email_address and status fields. We need to use JSON.stringify() to convert the fields into a single string.

Mailchimp’s endpoint is a POST call, so let’s set the method to that, and additionally define a headers object so we can pass Content-Type, which will be application/json.

export default async (req, res) => {
  const email = req.body.emailAddress
  
  try {
    const response = await fetch('https://{dc}.api.mailchimp.com/3.0/lists/{listId}/members', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email_address: email,
        status: 'subscribed'
      })
    })
  } catch { }
}

NOTE: To create the complete URL for your Mailchimp mailing list, you will need to find the dc location and listId for your account in Mailchimp's dashboard.

One last step is to add the Authorization field to the header. This will authenticate our request with Mailchimp's server with HTTP Basic Authentication using the API key created earlier. An easy way to create the Authorization token is using Postman's authorization tool. You can also create it manually by encoding your Mailchimp username and API key in Base64.

The authorization token needs to be passed in the headers object, but we should avoid keeping sensitive data like tokens, keys, and passwords unencrypted as strings in a file. Instead, let's create an environment variable that will be encrypted and stored securely outside of our code. Our app will find and use it automatically.

Create a .env file in the root of the project. This will store a list of environment variables as key-value pairs. The key can be anything, and the value will be the Base64 encoded token for Mailchimp. Remember to wrap the value in quotes.

MAILCHIMP_SECRET="Basic [email protected]"

Don’t forget to ignore this file in your source control — we don’t want to be syncing this plaintext file. It is best to recreate this file wherever your code will run. This file will help run your code on your local machine, and you can set up environment variables on most cloud platforms.

Once the token is saved, we can pull it from the env object and send it in our fetch request:

const { MAILCHIMP_AUTH: secret } = process.env // TAKE THE VALUE FROM THE ENV OBJECT

export default async (req, res) => {
  const email = req.body.emailAddress
  try {
    const response = await fetch('https://{dc}.api.mailchimp.com/3.0/lists/{listId}/members', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': secret, // REFER TO THE VARIABLE HERE
      },
      body: JSON.stringify({
        email_address: email,
        status: 'subscribed'
      })
    })
  } catch { }
}

Now we just need to return the correct response from our API depending on the response we get from Mailchimp. We will only send back a response status, and leave the response body empty since there is no data to be communicated back to the browser. To keep things simple in this tutorial, if Mailchimp returns a 200 response status, we will also return a 200. If Mailchimp returns anything else, we will return a 400 status. It either subscribed the user, or it didn't.

const { MAILCHIMP_AUTH: secret } = process.env

export default async (req, res) => {
  const email = req.body.emailAddress
  try {
    const response = await fetch('https://{dc}.api.mailchimp.com/3.0/lists/{listId}/members', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': secret,
      },
      body: JSON.stringify({
        email_address: email,
        status: 'subscribed'
      })
    })
    
    
    if (response.status === 200) {
      res.statusCode = 200
      res.end()
    } else {
      res.statusCode = 400
      res.end()
    }
  } catch { }
}

Now, fetch will throw an error if the call fails. This could be due to a network issue or a legitimate error returned from the Mailchimp API. This error will be caught in the catch block, so let's make sure it returns a response too.

import axios from 'axios'

const { MAILCHIMP_SECRET: secret } = process.env

export default async (req, res) => {
  const email = req.body.emailAddress
	try {
    const response = await axios({
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': secret,
      },
      url: 'https://{dc}.api.mailchimp.com/3.0/lists/{list_id}/members',
      data: {
        email_address: email,
        status: 'subscribed'
      }
    })
    
    if (response.status === 200) {
      res.statusCode = 200
      res.end()
    } else {
      res.statusCode = 400
      res.end()
    }
  } catch { }
}

And that's it! We have an API that will call Mailchimp with an email address and return a status code depending on Mailchimp's response. If you run the development server, you can test this in Postman by sending an email address in the body of a POST request. In response, we will either get a 200 code or 400 code, just as we coded for.

{
    "emailAddress" : "[email protected]"
}

In the next part of this tutorial, we will set up some validation and security in our API before deploying to the web, and we will also set up the frontend component — that is, the form itself. Stay tuned!

Kartik's Newsletter

Subscribe to get new posts and the latest updates from me right in your inbox

Your email address will never be sold or shared