How to deploy multiple micro-services under one API domain with Serverless

Written by Alex DeBrieEdit this post

In this post, I'll show you how to put multiple Serverless services on the same domain name. This is the most requested issue in the Serverless repo right now but is a tricky feature to implement directly within the Framework.

Using the power of Serverless and the serverless-domain-manager plugin, we can use API Gateway's base path mappings to handle this. Follow the instructions below to deploy your two services to the same domain.

If you already have your own services set up and just want the simple instructions, skip to the TL;DR section below.

#Getting Started

To get started, you'll need the Serverless Framework installed.

You should also have your desired domain name registered through AWS. Read the documentation on that here. You should also register a certificate for that domain through the AWS Certificate Manager. If you need help with that, read the Getting a certificate for your domain section of my previous post on using API Gateway with Serverless.

#Deploying your two services

Before we dive it, let's discuss exactly what we're trying to build. Imagine you have an e-commerce store which is a single-page application that consumes a backend REST API. Your REST API is hosted at api.mycompany.com, and you have two services: users and products.

You would like all users requests to be accessed at api.mycompany.com/users while all products requests would be accessed at api.mycompany.com/products. Further, you would like to separate these two services so they could be deployed independently -- changes to a products endpoint wouldn't require a redeploy of all users functions as well.

For this example, do the following:

First, create a new directory for your application:

$ mkdir api-gateway-application
$ cd api-gateway-application

Then, create a directory for your users service:

$ mkdir users-service
$ cd users-service

In your users-service directory, add the following serverless.yml file:

# serverless.yml

service: users-service

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-east-1
  environment:
    SERVICE_NAME: ${self:service}

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

Then add the following as handler.py:

# handler.py

import os


def hello(event, context):
    response = {
        "statusCode": 200,
        "body": "Hello from the {}!".format(os.environ.get('SERVICE_NAME'))
    }

    return response

This is a super simple service with a single endpoint (/hello) that will return the name of the service. To test it, deploy the service:

$ sls deploy
...
Service Information
service: users-service
stage: dev
region: us-east-1
stack: users-service-dev
api keys:
  None
endpoints:
  GET - https://n0benf6jn4.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
  hello: users-service-dev-hello

Copy and paste the endpoint into your browser, and you should see the following message:

User Service Example

As you can see, the URL isn't very friendly. We'll fix that during this walkthrough. To finish our setup, let's make a products service as well. Move up a level in your directory structure, then copy the users-service into a products-service directory:

$ cd ..
$ cp -r users-service/ products-service
$ cd products-service

Then edit the name of the service in your products-service serverless.yml:

# serverless.yml

service: products-service

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-east-1
  environment:
    SERVICE_NAME: ${self:service}

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

Run sls deploy to deploy the products-service, and make sure it's working in your browser:

Product Service Example

Again, it's an ugly URL, which we're going to change soon.

#Adding your services to your custom domain

Now that we have our two services set up, let's add them to a custom domain. You should still be in your products-service. Let's install the serverless-domain-manager plugin:

$ npm init -f
$ npm install serverless-domain-manager

Then add the configuration to your serverless.yml:

# serverless.yml

service: products-service

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-east-1
  environment:
    SERVICE_NAME: ${self:service}

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

plugins:
  - serverless-domain-manager

custom:
  customDomain:
    domainName: 'api.mycompany.com' # Change this to your domain.
    basePath: 'products' # This will be prefixed to all routes
    stage: ${self:provider.stage}
    createRoute53Record: true

We've added two sections to the serverless.yml. First, we registered the serverless-domain-manager in the plugins block. Then, we configured the plugin via the customDomain section of the custom block.

Note the basePath attribute that we're configuring. This will be prefixed to every route in our products-service. Thus, our route that is registered as /hello will actually be located at products/hello.

If you haven't previously registered this domain with API Gateway, you'll need to register it:

$ serverless create_domain
Serverless: Domain was created, may take up to 40 mins to be initialized

As the output notes, it can take up to 40 minutes to provision this in AWS. This is a one-time setup cost.

Once your domain is set up, deploy your service with sls deploy. Once the deploy is done, your endpoint will be available at api.mycompany.com/products/hello:

Products Service Base Path

That's a much cleaner URL!

Let's do the same with our users service. Change into that directory:

$ cd ../users-service

and follow the same steps as above. Install the serverless-domain-manager plugin:

$ npm init -f
$ npm install serverless-domain-manager

and add the config to your serverless.yml:

# serverless.yml

service: users-service

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-east-1
  environment:
    SERVICE_NAME: ${self:service}

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

plugins:
  - serverless-domain-manager

custom:
  customDomain:
    domainName: 'api.mycompany.com' # Change this to your domain.
    basePath: 'users' # This will be prefixed to all routes
    stage: ${self:provider.stage}
    createRoute53Record: true

Note that the basePath in this one is users, which will be prefixed to all routes in the users-service.

You don't need to run serverless create_domain again. Because you created the domain already, it is available for any services that want to use it.

Run sls deploy to deploy the users service, then check it in your browser:

Users Service Base Path

That's it! Now you easily separate your functions into services while still keeping them on the same domain. You're not limited to two services on this domain -- as you add additional services, just use a new basePath to add it to your domain.

#TL;DR

If you already have multiple services set up and are looking to add them to the same domain, follow these steps.

Before you begin, you'll need to get a certificate for your domain with the AWS Certificate Manager and register your domain with API Gateway. To do that, follow the steps in my previous post on using a custom domain with API Gateway and Serverless. Stop after the step that says sls create_domain.

In each service, install the serverless-domain-manager plugin:

$ npm install serverless-domain-manager

Then, add the following configuration to your serverless.yml:

# serverless.yml

plugins:
  - serverless-domain-manager

custom:
  customDomain:
    domainName: 'api.mycompany.com' # Change this to your domain.
    basePath: 'myprefix' # This will be prefixed to all routes
    stage: ${self:provider.stage}
    createRoute53Record: true

Make sure you change the domainName value to the domain name you want to use. Change the basePath value to the prefix you want for your routes in that service. For example, if you want your routes to start with /products/, the basePath value should be products.

Then, run sls deploy to get your service deployed to your custom domain with a base path!

About Alex DeBrie

Alex is a data engineer at Serverless. He is an ex-lawyer who loves Python, basketball, and his family.

Serverless Blog

The blog on serverless & event-driven compute

New to serverless?

To get started, pop open your terminal & run

npm install serverless -g

how? learn more

Subscribe

Join 12,000+ other serverless devs & keep up to speed on the latest serverless trends

Comments