Repository: serverless/serverless-azure-functions
Pull request #312 (2019-09-05 22:29:33)

feat: APIM feature updates
APIM configuration can easily bloat your serverless configuration yaml.  This PR reduces a lot of the boilerplate APIM configuration in many use cases except for when direct customization is required.

- [x] Infers APIM operation configuration from HTTP binding
- [x] Will automatically generate API just by setting `apm: true` in serverless yaml
- [x] Will automatically generate default backend pointing to the `/api` endpoint of your function app
- [x] Now supports configuring multiple API's in a single configuration
- [x] Now supports configuring multiple Backends in a single configuration
- [x] Ability to link an APIM operation to a specific API and backend


Patch:
diff --git a/docs/examples/apim.md b/docs/examples/apim.md
index 7faf378..ab6c57b 100644
--- a/docs/examples/apim.md
+++ b/docs/examples/apim.md
@@ -2,11 +2,44 @@
 
 [API Management](https://azure.microsoft.com/en-us/services/api-management/) is an Azure Service for publishing, managing, securing and monitoring APIs. It can be deployed along with your Serverless function app by specifying its configuration in `serverless.yml`. Here is a basic example of how to configure API Mangement:
 
-## Simple Handler
+## Simple Configuration
+Simply setting `apim: true` in your configuration will automatically deploy a consumption based APIM resource to Azure.  By default it will create a API with path of `/api` in your APIM instance and will map all operations defined in the serverless yaml to your function app.
 
 ```yaml
 service: greeter
 
+provider:
+  prefix: greeter
+  name: azure
+  # Default to West US, allow for command line arg --region to override
+  region: ${opt:region, 'westus'}
+  # Default to dev, allow for command line arg -- stage to override
+  stage: ${opt:stage, 'dev'}
+  # Azure subscription ID for deployment
+  subscriptionId: 00000000-0000-0000-0000-000000000000
+
+  # Start of your API Management configuration
+  apim: true
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: src/handlers/hello.handler
+    events:
+      - http: true
+        x-azure-settings:
+          methods:
+            - GET
+          authLevel : function
+```
+
+## Full Configuration
+In this example you can see the configuration support is quite verbose.  You have the ability to create multiple APIs and Backends as well as associate an operation to a specific api/backend.  If the operation is not specifically defined it will default to the first API / Backend that has been defined.
+```yaml
+service: greeter
+
 provider:
   prefix: greeter
   name: azure
@@ -20,25 +53,48 @@ provider:
   # Start of your API Management configuration
   apim:
     # API specifications
-    api:
-      # Name of the API
-      name: v1
-      subscriptionRequired: false
-      # Display name
-      displayName: v1
-      # Description of API
-      description: V1 sample app APIs
-      # HTTP protocols allowed
-      protocols:
-        - https
-      # Base path of API calls
-      path: v1
-      # Tags for ARM resource
-      tags:
-        - tag1
-        - tag2
-      # No authorization
-      authorization: none
+    apis:
+        # Name of the API
+      - name: products-api
+        subscriptionRequired: false
+        # Display name
+        displayName: Products API
+        # Description of API
+        description: The Products REST API
+        # HTTP protocols allowed
+        protocols:
+          - https
+        # Base path of API calls
+        path: products
+        # Tags for ARM resource
+        tags:
+          - tag1
+          - tag2
+        # No authorization
+        authorization: none
+        # Name of the API
+      - name: categories-api
+        subscriptionRequired: false
+        # Display name
+        displayName: Categories API
+        # Description of API
+        description: The Categories REST API
+        # HTTP protocols allowed
+        protocols:
+          - https
+        # Base path of API calls
+        path: categories
+        # Tags for ARM resource
+        tags:
+          - tag1
+          - tag2
+        # No authorization
+        authorization: none
+    backends:
+      - name: products-backend
+        url: api/products
+      - name: categories-backend
+        url: api/categories
     # CORS Settings for APIM
     cors:
       allowCredentials: false
@@ -59,26 +115,48 @@ plugins:
   - serverless-azure-functions
 
 functions:
-  hello:
-    handler: src/handlers/hello.handler
+  getProducts:
+    handler: src/handlers/getProducts.handler
 
     # API Management configuration for `hello` handler
     apim:
+      # The API to attach this operation
+      api: products-api
+      # The Backend use for the operation
+      backend: products-backend
       operations:
-        # GET operation for `hello` handler
+        # GET operation for `getProducts` handler
         - method: get
           # URL path for accessing handler
-          urlTemplate: /hello
+          urlTemplate: /
           # Display name inside Azure Portal
-          displayName: Hello
+          displayName: GetProducts
     events:
       - http: true
         x-azure-settings:
           methods:
             - GET
           authLevel : function
+  getCategories:
+    handler: src/handlers/getCategories.handler
+
+    # API Management configuration for `getCategories` handler
+    apim:
+      # The API to attach this operation
+      api: categories-api
+      # The Backend use for the operation
+      backend: categories-backend
+      operations:
+        # GET operation for `getCategories` handler
+        - method: get
+          # URL path for accessing handler
+          urlTemplate: /
+          # Display name inside Azure Portal
+          displayName: GetCategories
+    events:
       - http: true
         x-azure-settings:
-          direction: out
-          name: res
+          methods:
+            - GET
+          authLevel : function
 ```
diff --git a/package-lock.json b/package-lock.json
index bec65fb..b0bab97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1832,6 +1832,7 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
       "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+      "optional": true,
       "requires": {
         "kind-of": "^3.0.2",
         "longest": "^1.0.1",
@@ -2004,6 +2005,7 @@
       "version": "4.10.1",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
       "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "optional": true,
       "requires": {
         "bn.js": "^4.0.0",
         "inherits": "^2.0.1",
@@ -2850,7 +2852,8 @@
     "bn.js": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "optional": true
     },
     "body-parser": {
       "version": "1.19.0",
@@ -2955,7 +2958,8 @@
     "brorand": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "optional": true
     },
     "browser-process-hrtime": {
       "version": "0.1.3",
@@ -2984,6 +2988,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "optional": true,
       "requires": {
         "buffer-xor": "^1.0.3",
         "cipher-base": "^1.0.0",
@@ -3020,6 +3025,7 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "optional": true,
       "requires": {
         "bn.js": "^4.1.0",
         "randombytes": "^2.0.1"
@@ -3116,7 +3122,8 @@
     "buffer-xor": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "optional": true
     },
     "builtin-modules": {
       "version": "1.1.1",
@@ -3303,6 +3310,7 @@
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
       "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -3666,6 +3674,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "optional": true,
       "requires": {
         "cipher-base": "^1.0.1",
         "inherits": "^2.0.1",
@@ -3678,6 +3687,7 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "optional": true,
       "requires": {
         "cipher-base": "^1.0.3",
         "create-hash": "^1.1.0",
@@ -4140,6 +4150,7 @@
       "version": "6.4.1",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
       "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
+      "optional": true,
       "requires": {
         "bn.js": "^4.4.0",
         "brorand": "^1.0.1",
@@ -4201,6 +4212,7 @@
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
       "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "optional": true,
       "requires": {
         "prr": "~1.0.1"
       }
@@ -4586,6 +4598,7 @@
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "optional": true,
       "requires": {
         "d": "1",
         "es5-ext": "~0.10.14"
@@ -4600,6 +4613,7 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
       "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "optional": true,
       "requires": {
         "md5.js": "^1.3.4",
         "safe-buffer": "^5.1.1"
@@ -5148,11 +5162,13 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -5170,11 +5186,13 @@
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5277,7 +5295,8 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5300,17 +5319,20 @@
         "minimatch": {
           "version": "3.0.4",
           "bundled": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -5327,6 +5349,7 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -5410,6 +5433,7 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5485,7 +5509,8 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5515,6 +5540,7 @@
         "string-width": {
           "version": "1.0.2",
           "bundled": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5571,11 +5597,13 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         }
       }
     },
@@ -5877,6 +5905,7 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
       "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -5886,6 +5915,7 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
       "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.3",
         "minimalistic-assert": "^1.0.1"
@@ -5895,6 +5925,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
       "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "optional": true,
       "requires": {
         "hash.js": "^1.0.3",
         "minimalistic-assert": "^1.0.0",
@@ -6213,7 +6244,8 @@
     "is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "optional": true
     },
     "is-fullwidth-code-point": {
       "version": "1.0.0",
@@ -7879,7 +7911,8 @@
     "longest": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
-      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
+      "optional": true
     },
     "loose-envify": {
       "version": "1.4.0",
@@ -7986,6 +8019,7 @@
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
       "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "optional": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1",
@@ -8011,6 +8045,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
       "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "optional": true,
       "requires": {
         "errno": "^0.1.3",
         "readable-stream": "^2.0.1"
@@ -8101,12 +8136,14 @@
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "optional": true
     },
     "minimalistic-crypto-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "optional": true
     },
     "minimatch": {
       "version": "3.0.4",
@@ -8744,6 +8781,7 @@
       "version": "5.1.4",
       "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
       "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
+      "optional": true,
       "requires": {
         "asn1.js": "^4.0.0",
         "browserify-aes": "^1.0.0",
@@ -8852,6 +8890,7 @@
       "version": "3.0.17",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
       "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "optional": true,
       "requires": {
         "create-hash": "^1.1.2",
         "create-hmac": "^1.1.4",
@@ -9054,7 +9093,8 @@
     "prr": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
-      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+      "optional": true
     },
     "pseudomap": {
       "version": "1.0.2",
@@ -9121,6 +9161,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "optional": true,
       "requires": {
         "safe-buffer": "^5.1.0"
       }
@@ -9533,6 +9574,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
       "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "optional": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1"
@@ -9963,6 +10005,7 @@
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -10158,7 +10201,8 @@
     "source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
-      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+      "optional": true
     },
     "source-map": {
       "version": "0.7.3",
@@ -10715,7 +10759,8 @@
     "tapable": {
       "version": "0.2.9",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz",
-      "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A=="
+      "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==",
+      "optional": true
     },
     "tar-stream": {
       "version": "1.6.2",
@@ -11571,6 +11616,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",
       "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==",
+      "optional": true,
       "requires": {
         "source-list-map": "^2.0.0",
         "source-map": "~0.6.1"
@@ -11579,7 +11625,8 @@
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "optional": true
         }
       }
     },
diff --git a/src/index.ts b/src/index.ts
index f628de4..5946338 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,14 +11,12 @@ import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
 import { AzureLoginPlugin } from "./plugins/login/azureLoginPlugin";
-import { AzureApimServicePlugin } from "./plugins/apim/azureApimServicePlugin";
-import { AzureApimFunctionPlugin } from "./plugins/apim/azureApimFunctionPlugin";
+import { AzureApimPlugin } from "./plugins/apim/azureApimPlugin";
 import { AzureFuncPlugin } from "./plugins/func/azureFuncPlugin";
 import { AzureOfflinePlugin } from "./plugins/offline/azureOfflinePlugin"
 import { AzureRollbackPlugin } from "./plugins/rollback/azureRollbackPlugin"
 import { AzureKeyVaultPlugin } from "./plugins/identity/azureKeyVaultPlugin"
 
-
 export default class AzureIndex {
   public constructor(private serverless: Serverless, private options) {
     this.serverless.setProvider(AzureProvider.getProviderName(), new AzureProvider(serverless) as any);
@@ -30,8 +28,7 @@ export default class AzureIndex {
     // Refactored
     this.serverless.pluginManager.addPlugin(AzureLoginPlugin);
     this.serverless.pluginManager.addPlugin(AzureDeployPlugin);
-    this.serverless.pluginManager.addPlugin(AzureApimServicePlugin);
-    this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin);
+    this.serverless.pluginManager.addPlugin(AzureApimPlugin);
     this.serverless.pluginManager.addPlugin(AzureFuncPlugin);
     this.serverless.pluginManager.addPlugin(AzureOfflinePlugin);
     this.serverless.pluginManager.addPlugin(AzureRollbackPlugin);
diff --git a/src/models/apiManagement.ts b/src/models/apiManagement.ts
index 3fc656d..d11270a 100644
--- a/src/models/apiManagement.ts
+++ b/src/models/apiManagement.ts
@@ -1,4 +1,4 @@
-import { OperationContract, ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models";
+import { ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models";
 
 /**
  * Defines the serverless APIM configuration
@@ -7,9 +7,9 @@ export interface ApiManagementConfig {
   /** The name of the APIM azure resource */
   name: string;
   /** The API contract configuration */
-  api: ApiContract;
+  apis: ApiContract[];
   /** The API's backend contract configuration */
-  backend?: BackendContract;
+  backends?: BackendContract[];
   /** The API's CORS policy */
   cors?: ApiCorsPolicy;
   sku?: {
@@ -20,16 +20,6 @@ export interface ApiManagementConfig {
   publisherName?: string;
 }
 
-/**
- * Defines the APIM API Operation configuration
- */
-export interface ApiOperationOptions {
-  /** The name of the serverless function */
-  function: string;
-  /** The APIM operation contract configuration */
-  operation: OperationContract;
-}
-
 /**
  * Defines an APIM API CORS (cross origin resource sharing) policy
  */
@@ -44,4 +34,4 @@ export interface ApiCorsPolicy {
   allowedHeaders: string[];
   /** A list of headers exposed during OPTION preflight requests */
   exposeHeaders: string[];
-}
\ No newline at end of file
+}
diff --git a/src/plugins/apim/azureApimFunctionPlugin.ts b/src/plugins/apim/azureApimFunctionPlugin.ts
deleted file mode 100644
index c81300c..0000000
--- a/src/plugins/apim/azureApimFunctionPlugin.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import Serverless from "serverless";
-import { ApimService } from "../../services/apimService";
-import { AzureBasePlugin } from "../azureBasePlugin";
-
-export class AzureApimFunctionPlugin extends AzureBasePlugin {
-
-  public constructor(serverless: Serverless, options: Serverless.Options) {
-    super(serverless, options);
-    this.hooks = {
-      "after:deploy:function:deploy": this.deploy.bind(this)
-    };
-  }
-
-  private async deploy() {
-    this.serverless.cli.log("Starting APIM function deployment");
-
-    const apimService = new ApimService(this.serverless, this.options);
-    const service = await apimService.get();
-    const api = await apimService.getApi();
-
-    await apimService.deployFunction(service, api, this.options);
-
-    this.log("Finished APIM function deployment");
-  }
-}
diff --git a/src/plugins/apim/azureApimServicePlugin.ts b/src/plugins/apim/azureApimPlugin.ts
similarity index 71%
rename from src/plugins/apim/azureApimServicePlugin.ts
rename to src/plugins/apim/azureApimPlugin.ts
index d900fc5..500f441 100644
--- a/src/plugins/apim/azureApimServicePlugin.ts
+++ b/src/plugins/apim/azureApimPlugin.ts
@@ -2,12 +2,12 @@ import Serverless from "serverless";
 import { ApimService } from "../../services/apimService";
 import { AzureBasePlugin } from "../azureBasePlugin";
 
-export class AzureApimServicePlugin extends AzureBasePlugin {
+export class AzureApimPlugin extends AzureBasePlugin {
 
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
     this.hooks = {
-      "after:deploy:deploy": this.deploy.bind(this)
+      "after:deploy:deploy": this.deploy.bind(this),
     };
   }
 
@@ -20,9 +20,7 @@ export class AzureApimServicePlugin extends AzureBasePlugin {
     this.serverless.cli.log("Starting APIM service deployment");
 
     const apimService = new ApimService(this.serverless, this.options);
-    const service = await apimService.get();
-    const api = await apimService.deployApi();
-    await apimService.deployFunctions(service, api);
+    await apimService.deploy();
 
     this.log("Finished APIM service deployment");
   }
diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts
index 4b1b8ea..8d7cf3e 100644
--- a/src/plugins/deploy/azureDeployPlugin.ts
+++ b/src/plugins/deploy/azureDeployPlugin.ts
@@ -4,6 +4,7 @@ import { FunctionAppService } from "../../services/functionAppService";
 import { AzureLoginOptions } from "../../services/loginService";
 import { ResourceService } from "../../services/resourceService";
 import { AzureBasePlugin } from "../azureBasePlugin";
+import { ApimService } from "../../services/apimService";
 
 export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
   public commands: any;
@@ -14,65 +15,49 @@ export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
     this.hooks = {
       "deploy:deploy": this.deploy.bind(this),
       "deploy:list:list": this.list.bind(this),
+      "deploy:apim:apim": this.deployApim.bind(this),
     };
 
+    const deployOptions = {
+      resourceGroup: {
+        usage: "Resource group for the service",
+        shortcut: "g",
+      },
+      stage: {
+        usage: "Stage of service",
+        shortcut: "s"
+      },
+      region: {
+        usage: "Region of service",
+        shortcut: "r"
+      },
+      subscriptionId: {
+        usage: "Sets the Azure subscription ID",
+        shortcut: "i",
+      },
+      function: {
+        usage: "Deployment of individual function - NOT SUPPORTED",
+        shortcut: "f",
+      }
+    }
+
     this.commands = {
       deploy: {
         commands: {
           list: {
             usage: "List deployments",
-            lifecycleEvents: [
-              "list"
-            ]
-          },
-          options: {
-            resourceGroup: {
-              usage: "Resource group for the service",
-              shortcut: "g",
-            },
-            stage: {
-              usage: "Stage of service",
-              shortcut: "s"
-            },
-            region: {
-              usage: "Region of service",
-              shortcut: "r"
-            },
-            subscriptionId: {
-              usage: "Sets the Azure subscription ID",
-              shortcut: "i",
-            },
-            function: {
-              usage: "Deployment of individual function - NOT SUPPORTED",
-              shortcut: "f",
+            lifecycleEvents: ["list"],
+            options: {
+              ...deployOptions
             }
+          },
+          apim: {
+            usage: "Deploys APIM",
+            lifecycleEvents: ["apim"]
           }
         },
         options: {
-          resourceGroup: {
-            usage: "Resource group for the service",
-            shortcut: "g",
-          },
-          stage: {
-            usage: "Stage of service",
-            shortcut: "s"
-          },
-          region: {
-            usage: "Region of service",
-            shortcut: "r"
-          },
-          subscriptionId: {
-            usage: "Sets the Azure subscription ID",
-            shortcut: "i",
-          },
-          package: {
-            usage: "Package to deploy",
-            shortcut: "p",
-          },
-          function: {
-            usage: "Deployment of individual function - NOT SUPPORTED",
-            shortcut: "f",
-          }
+          ...deployOptions
         }
       }
     }
@@ -98,6 +83,24 @@ export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
     await functionAppService.uploadFunctions(functionApp);
   }
 
+  /**
+   * Deploys APIM if configured
+   */
+  private async deployApim() {
+    const apimConfig = this.serverless.service.provider["apim"];
+    if (!apimConfig) {
+      this.log("No APIM configuration found");
+      return Promise.resolve();
+    }
+
+    this.serverless.cli.log("Starting APIM service deployment");
+
+    const apimService = new ApimService(this.serverless, this.options);
+    await apimService.deploy();
+
+    this.log("Finished APIM service deployment");
+  }
+
   /**
    * Check to see if user tried to target an individual function for deployment or deployment list
    * Throws error if `function` is specified
diff --git a/src/plugins/identity/azureKeyVaultPlugin.ts b/src/plugins/identity/azureKeyVaultPlugin.ts
index 999d3d3..8be800e 100644
--- a/src/plugins/identity/azureKeyVaultPlugin.ts
+++ b/src/plugins/identity/azureKeyVaultPlugin.ts
@@ -15,13 +15,11 @@ export class AzureKeyVaultPlugin extends AzureBasePlugin {
     if (!keyVaultConfig) {
       return Promise.resolve();
     }
-    this.serverless.cli.log("Starting KeyVault service setup");
+    this.log("Starting KeyVault service setup");
 
     const keyVaultService = new AzureKeyVaultService(this.serverless, this.options);
-    const result = await keyVaultService.setPolicy(keyVaultConfig);
+    await keyVaultService.setPolicy(keyVaultConfig);
 
-    this.serverless.cli.log("Finished KeyVault service setup");
-
-    return result;
+    this.log("Finished KeyVault service setup");
   }
 }
diff --git a/src/plugins/login/loginHooks.ts b/src/plugins/login/loginHooks.ts
index 97db4a3..44f4475 100644
--- a/src/plugins/login/loginHooks.ts
+++ b/src/plugins/login/loginHooks.ts
@@ -4,6 +4,7 @@
 export const loginHooks = [
   "deploy:list:list",
   "deploy:deploy",
+  "deploy:apim:apim",
   "invoke:invoke",
   "rollback:rollback",
   "remove:remove",
diff --git a/src/services/apimService.ts b/src/services/apimService.ts
index f66a722..22ca474 100644
--- a/src/services/apimService.ts
+++ b/src/services/apimService.ts
@@ -3,10 +3,10 @@ import xml from "xml";
 import { ApiManagementClient } from "@azure/arm-apimanagement";
 import { FunctionAppService } from "./functionAppService";
 import { BaseService } from "./baseService";
-import { ApiManagementConfig, ApiOperationOptions, ApiCorsPolicy } from "../models/apiManagement";
+import { ApiManagementConfig, ApiCorsPolicy } from "../models/apiManagement";
 import {
-  ApiContract, BackendContract, OperationContract,
-  PropertyContract, ApiManagementServiceResource,
+  ApiContract, OperationContract,
+  PropertyContract, ApiManagementServiceResource, BackendContract,
 } from "@azure/arm-apimanagement/esm/models";
 import { Site } from "@azure/arm-appservice/esm/models";
 import { Guard } from "../shared/guard";
@@ -28,12 +28,23 @@ export class ApimService extends BaseService {
       return;
     }
 
+    if (typeof (this.apimConfig) === "boolean") {
+      this.apimConfig = {
+        name: null,
+        apis: [],
+      };
+    }
+
     if (!this.apimConfig.name) {
       this.apimConfig.name = ApimResource.getResourceName(this.config);
     }
 
-    if (!this.apimConfig.backend) {
-      this.apimConfig.backend = {} as any;
+    if (!this.apimConfig.apis) {
+      this.apimConfig.apis = [];
+    }
+
+    if (!this.apimConfig.backends) {
+      this.apimConfig.backends = [];
     }
 
     this.apimClient = new ApiManagementClient(this.credentials, this.subscriptionId);
@@ -55,13 +66,19 @@ export class ApimService extends BaseService {
     }
   }
 
-  public async getApi(): Promise<ApiContract> {
-    if (!(this.apimConfig && this.apimConfig.api && this.apimConfig.api.name)) {
+  /**
+   * Gets the APIM API by API name
+   * @param apiName The API to retrieve
+   */
+  public async getApi(apiName: string): Promise<ApiContract> {
+    Guard.empty(apiName);
+
+    if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
     try {
-      return await this.apimClient.api.get(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name);
+      return await this.apimClient.api.get(this.resourceGroup, this.apimConfig.name, apiName);
     } catch (err) {
       return null;
     }
@@ -70,26 +87,30 @@ export class ApimService extends BaseService {
   /**
    * Deploys the APIM top level api
    */
-  public async deployApi() {
+  public async deploy() {
     if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
     const functionApp = await this.functionAppService.get();
-
-    const api = await this.ensureApi();
+    this.setApimDefaults(functionApp);
     await this.ensureFunctionAppKeys(functionApp);
-    await this.ensureBackend(functionApp);
 
-    return api;
+    const resource = await this.get();
+    const apiTasks = this.apimConfig.apis.map((api) => this.ensureApi(api));
+    const backendTasks = this.apimConfig.backends.map((backend) => this.ensureBackend(functionApp, backend));
+
+    await Promise.all(apiTasks);
+    await Promise.all(backendTasks);
+
+    await this.deployFunctions(functionApp, resource);
   }
 
   /**
    * Deploys all the functions of the serverless service to APIM
    */
-  public async deployFunctions(service: ApiManagementServiceResource, api: ApiContract) {
-    Guard.null(service);
-    Guard.null(api);
+  public async deployFunctions(functionApp: Site, resource: ApiManagementServiceResource) {
+    Guard.null(resource);
 
     if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
@@ -99,56 +120,103 @@ export class ApimService extends BaseService {
 
     const deployApiTasks = this.serverless.service
       .getAllFunctions()
-      .map((functionName) => this.deployFunction(service, api, { function: functionName }));
+      .map((functionName) => this.deployFunction(functionApp, resource, functionName));
 
-    return Promise.all(deployApiTasks);
+    return await Promise.all(deployApiTasks);
   }
 
   /**
    * Deploys the specified serverless function  to APIM
    * @param options
    */
-  public async deployFunction(service: ApiManagementServiceResource, api: ApiContract, options) {
-    Guard.null(service);
-    Guard.null(api);
-    Guard.null(options);
+  public async deployFunction(functionApp: Site, resource: ApiManagementServiceResource, functionName: string) {
+    Guard.null(functionApp);
+    Guard.null(resource);
+    Guard.empty(functionName);
 
-    const functionConfig = this.serverless.service["functions"][options.function];
+    const functionConfig = this.serverless.service["functions"][functionName];
+    functionConfig.name = functionName;
 
-    if (!(functionConfig && functionConfig.apim)) {
+    const httpEvent = functionConfig.events.find((event) => event.http);
+    if (!httpEvent) {
       return;
     }
 
-    const tasks = functionConfig.apim.operations.map((operation) => {
-      return this.deployOperation(service, api, {
-        function: options.function,
-        operation,
-      });
-    });
+    const httpConfig = httpEvent["x-azure-settings"];
+
+    // Infer APIM operation configuration from HTTP event if not already set
+    if (!functionConfig.apim) {
+      const operations = httpConfig.methods.map((method) => {
+        return {
+          name: functionConfig.name,
+          displayName: functionConfig.name,
+          urlTemplate: httpConfig.route || functionConfig.name,
+          method: method,
+          templateParameters: this.getTemplateParameters(httpConfig.route)
+        };
+      })
+
+      functionConfig.apim = { operations };
+    }
+
+    // Lookup api mapping
+    const apiContract = functionConfig.apim.api
+      ? this.apimConfig.apis.find((api) => api.name === functionConfig.apim.api)
+      : this.apimConfig.apis[0];
+
+    // Lookup backend mapping
+    const backendContract = functionConfig.apim.backend
+      ? this.apimConfig.backends.find((backend) => backend.name === functionConfig.apim.backend)
+      : this.apimConfig.backends[0];
+
+    const tasks = functionConfig.apim.operations
+      .map((operation) => this.deployOperation(resource, apiContract, backendContract, operation, functionName));
 
     await Promise.all(tasks);
   }
 
+  /**
+   * Retrieves the template parameter referenced in the route template
+   * @param route The route template to inspect
+   */
+  private getTemplateParameters(route: string) {
+    const regex = new RegExp(/{(\w+)}/g);
+    const matches = [];
+    while (true) {
+      const match = regex.exec(route);
+      if (!match) {
+        break;
+      }
+
+      matches.push(match);
+    };
+
+    if (matches.length === 0) {
+      return null;
+    }
+
+    return matches.map((match) => ({
+      name: match[1],
+      type: "string",
+    }));
+  }
+
   /**
    * Deploys the APIM API referenced by the serverless service
    */
-  private async ensureApi(): Promise<ApiContract> {
-    this.log("-> Deploying API");
+  private async ensureApi(apiContract: ApiContract): Promise<ApiContract> {
+    this.log(`-> Deploying API: ${apiContract.name}`);
 
     try {
-      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
+      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.apimConfig.name, apiContract.name, {
+        ...apiContract,
         isCurrent: true,
-        subscriptionRequired: this.apimConfig.api.subscriptionRequired,
-        displayName: this.apimConfig.api.displayName,
-        description: this.apimConfig.api.description,
-        path: this.apimConfig.api.path,
-        protocols: this.apimConfig.api.protocols,
       });
 
       if (this.apimConfig.cors) {
         this.log("-> Deploying CORS policy");
 
-        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
+        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, apiContract.name, {
           format: "rawxml",
           value: this.createCorsXmlPolicy(this.apimConfig.cors)
         });
@@ -166,24 +234,25 @@ export class ApimService extends BaseService {
    * Deploys the APIM Backend referenced by the serverless service
    * @param functionAppUrl The host name for the deployed function app
    */
-  private async ensureBackend(functionApp: Site): Promise<BackendContract> {
-    const backendUrl = `https://${functionApp.defaultHostName}/api`;
+  private async ensureBackend(functionApp: Site, backendContract: BackendContract): Promise<BackendContract> {
+    const backendUrl = `https://${functionApp.defaultHostName}/${backendContract.url}`;
 
-    this.log(`-> Deploying API Backend ${functionApp.name} = ${backendUrl}`);
+    this.log(`-> Deploying API Backend: ${backendContract.name} => ${backendUrl}`);
     try {
       const functionAppResourceId = `https://management.azure.com${functionApp.id}`;
 
-      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.serviceName, {
+      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.apimConfig.name, backendContract.name, {
         credentials: {
           header: {
             "x-functions-key": [`{{${this.serviceName}-key}}`],
           },
         },
-        title: this.apimConfig.backend.title || functionApp.name,
-        tls: this.apimConfig.backend.tls,
-        proxy: this.apimConfig.backend.proxy,
-        description: this.apimConfig.backend.description,
-        protocol: this.apimConfig.backend.protocol || "http",
+        name: backendContract.name,
+        title: backendContract.title || functionApp.name,
+        tls: backendContract.tls,
+        proxy: backendContract.proxy,
+        description: backendContract.description,
+        protocol: backendContract.protocol || "http",
         resourceId: functionAppResourceId,
         url: backendUrl,
       });
@@ -199,44 +268,40 @@ export class ApimService extends BaseService {
    * @param serverless The serverless framework
    * @param options The plugin options
    */
-  private async deployOperation(
-    service: ApiManagementServiceResource,
-    api: ApiContract,
-    options: ApiOperationOptions,
-  ): Promise<OperationContract> {
+  private async deployOperation(resource: ApiManagementServiceResource, api: ApiContract, backend: BackendContract, operation: OperationContract, functionName: string): Promise<OperationContract> {
     try {
       const client = new ApiManagementClient(this.credentials, this.subscriptionId);
 
       const operationConfig: OperationContract = {
-        displayName: options.operation.displayName || options.function,
-        description: options.operation.description || "",
-        method: options.operation.method,
-        urlTemplate: options.operation.urlTemplate,
-        templateParameters: options.operation.templateParameters || [],
-        responses: options.operation.responses || [],
+        displayName: operation.displayName || functionName,
+        description: operation.description || "",
+        method: operation.method,
+        urlTemplate: operation.urlTemplate,
+        templateParameters: operation.templateParameters || [],
+        responses: operation.responses || [],
       };
 
       // Ensure a single path seperator in the operation path
       const operationPath = `/${api.path}/${operationConfig.urlTemplate}`.replace(/\/+/g, "/");
-      const operationUrl = `${service.gatewayUrl}${operationPath}`;
-      this.log(`--> Deploying API operation ${options.function}: ${operationConfig.method.toUpperCase()} ${operationUrl}`);
+      const operationUrl = `${resource.gatewayUrl}${operationPath}`;
+      this.log(`--> ${functionName}: [${operationConfig.method.toUpperCase()}] ${operationUrl}`);
 
-      const operation = await client.apiOperation.createOrUpdate(
+      const result = await client.apiOperation.createOrUpdate(
         this.resourceGroup,
         this.apimConfig.name,
-        this.apimConfig.api.name,
-        options.function,
+        api.name,
+        functionName,
         operationConfig,
       );
 
-      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, options.function, {
+      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, api.name, functionName, {
         format: "rawxml",
-        value: this.createApiOperationXmlPolicy(),
+        value: this.createApiOperationXmlPolicy(backend.name),
       });
 
-      return operation;
+      return result;
     } catch (e) {
-      this.log(`Error deploying API operation ${options.function}`);
+      this.log(`Error deploying API operation ${functionName}`);
       this.log(JSON.stringify(e.body, null, 4));
       throw e;
     }
@@ -267,7 +332,7 @@ export class ApimService extends BaseService {
   /**
    * Creates the XML payload that defines the API operation policy to link to the configured backend
    */
-  private createApiOperationXmlPolicy(): string {
+  private createApiOperationXmlPolicy(backendId: string): string {
     const operationPolicy = [{
       policies: [
         {
@@ -278,7 +343,7 @@ export class ApimService extends BaseService {
                 {
                   "_attr": {
                     "id": "apim-generated-policy",
-                    "backend-id": this.serviceName,
+                    "backend-id": backendId,
                   }
                 },
               ],
@@ -328,4 +393,45 @@ export class ApimService extends BaseService {
 
     return xml(policy, { indent: "\t" });
   }
+
+  /**
+   * Sets up APIM defaults if not explicitly defined
+   * @param functionApp The function app resource
+   */
+  private setApimDefaults(functionApp: Site) {
+    const defaultApi: ApiContract = {
+      isCurrent: true,
+      name: `${this.serviceName}-api`,
+      subscriptionRequired: false,
+      displayName: "API",
+      description: "",
+      path: "api",
+      protocols: ["http", "https"]
+    }
+
+    if (this.apimConfig.apis.length === 0) {
+      this.apimConfig.apis.push(defaultApi);
+    }
+
+    const functionAppResourceId = `https://management.azure.com${functionApp.id}`;
+
+    // Configure a default backend link if not explicity defined
+    const defaultBackend: BackendContract = {
+      credentials: {
+        header: {
+          "x-functions-key": [`{{${this.serviceName}-key}}`],
+        },
+      },
+      name: `${this.serviceName}-backend`,
+      title: functionApp.name,
+      description: "Function App Backend",
+      protocol: "http",
+      resourceId: functionAppResourceId,
+      url: "api"
+    };
+
+    if (this.apimConfig.backends.length === 0) {
+      this.apimConfig.backends.push(defaultBackend);
+    }
+  }
 }
diff --git a/src/services/azureKeyVaultService.ts b/src/services/azureKeyVaultService.ts
index 069ed19..8bb0c5a 100644
--- a/src/services/azureKeyVaultService.ts
+++ b/src/services/azureKeyVaultService.ts
@@ -18,8 +18,6 @@ export interface AzureKeyVaultConfig {
  * Services for the Key Vault Plugin
  */
 export class AzureKeyVaultService extends BaseService {
-  private funcApp: FunctionAppService;
-
   /**
    * Initialize key vault service and get function app
    * @param serverless Serverless object
@@ -27,7 +25,6 @@ export class AzureKeyVaultService extends BaseService {
    */
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
-    this.funcApp = new FunctionAppService(serverless, options);
   }
 
   /**
@@ -36,13 +33,15 @@ export class AzureKeyVaultService extends BaseService {
    */
   public async setPolicy(keyVaultConfig: AzureKeyVaultConfig) {
     const subscriptionID = this.subscriptionId;
+    const functionAppService = new FunctionAppService(this.serverless, this.options);
+    const keyVaultClient = new KeyVaultManagementClient(this.credentials, subscriptionID);
 
-    const func = await this.funcApp.get();
-    const identity = func.identity;
+    const functionApp = await functionAppService.get();
+    const identity = functionApp.identity;
     let vault: Vault;
-    const keyVaultClient = new KeyVaultManagementClient(this.credentials, subscriptionID);
+
     try {
-      vault = await keyVaultClient.vaults.get(keyVaultConfig.resourceGroup, keyVaultConfig.name)
+      vault = await keyVaultClient.vaults.get(keyVaultConfig.resourceGroup, keyVaultConfig.name);
     } catch (error) {
       throw new Error("Error: Specified vault not found")
     }
@@ -56,6 +55,6 @@ export class AzureKeyVaultService extends BaseService {
     }
     vault.properties.accessPolicies.push(newEntry);
 
-    return keyVaultClient.vaults.createOrUpdate(keyVaultConfig.resourceGroup, keyVaultConfig.name, {location: vault.location, properties: vault.properties})
+    await keyVaultClient.vaults.createOrUpdate(keyVaultConfig.resourceGroup, keyVaultConfig.name, {location: vault.location, properties: vault.properties});
   }
 }
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index e1a513a..2969e8e 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -49,9 +49,7 @@ export abstract class BaseService {
     this.storageAccountName = StorageAccountResource.getResourceName(this.config);
 
     if (!this.credentials && authenticate) {
-      throw new Error(
-        `Azure Credentials has not been set in ${this.constructor.name}`
-      );
+      throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`);
     }
   }
 
@@ -196,7 +194,7 @@ export abstract class BaseService {
    * Log message to Serverless CLI
    * @param message Message to log
    */
-  protected log(message: string, options?: ServerlessLogOptions, entity?: string,) {
+  protected log(message: string, options?: ServerlessLogOptions, entity?: string) {
     (this.serverless.cli.log as any)(message, entity, options);
   }
 
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 0045018..6942f8b 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -154,23 +154,26 @@ export class FunctionAppService extends BaseService {
     this.log("Deploying serverless functions...");
 
     const functionZipFile = this.getFunctionZipFile();
-    const blobUpload = this.uploadZippedArtifactToBlobStorage(functionZipFile);
 
     if (this.deploymentConfig.runFromBlobUrl) {
       this.log("Updating function app setting to run from external package...");
-      await blobUpload;
+      await this.uploadZippedArtifactToBlobStorage(functionZipFile);
+
       const sasUrl = await this.blobService.generateBlobSasTokenUrl(
         this.deploymentConfig.container,
         this.artifactName
-      )
+      );
+
       await this.updateFunctionAppSetting(
         functionApp,
         configConstants.runFromPackageSetting,
         sasUrl
-      )
+      );
     } else {
-      const functionAppUpload = this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile);
-      Promise.all([blobUpload, functionAppUpload]);
+      await Promise.all([
+        this.uploadZippedArtifactToBlobStorage(functionZipFile),
+        this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile)
+      ]);
     }
 
     this.log("Deployed serverless functions:")
@@ -184,7 +187,7 @@ export class FunctionAppService extends BaseService {
 
         if (httpConfig) {
           const method = httpConfig.methods[0].toUpperCase();
-          this.log(`-> ${functionConfig.name}: ${method} ${httpConfig.url}`);
+          this.log(`-> ${functionConfig.name}: [${method}] ${httpConfig.url}`);
         }
       }
     });
@@ -256,7 +259,7 @@ export class FunctionAppService extends BaseService {
   /**
    * Uploads artifact file to blob storage container
    */
-  private async uploadZippedArtifactToBlobStorage(functionZipFile: string) {
+  private async uploadZippedArtifactToBlobStorage(functionZipFile: string): Promise<void> {
     await this.blobService.initialize();
     await this.blobService.createContainerIfNotExists(this.deploymentConfig.container);
     await this.blobService.uploadFile(
diff --git a/src/services/loginService.ts b/src/services/loginService.ts
index 8ed84dc..d2ad10e 100644
--- a/src/services/loginService.ts
+++ b/src/services/loginService.ts
@@ -26,7 +26,7 @@ export class AzureLoginService extends BaseService {
    * set or via interactive login if environment variables are not set
    * @param options Options for different authentication methods
    */
-  public async login(options?: AzureTokenCredentialsOptions|InteractiveLoginOptions): Promise<AuthResponse> {
+  public async login(options?: AzureTokenCredentialsOptions | InteractiveLoginOptions): Promise<AuthResponse> {
     const subscriptionId = process.env.azureSubId;
     const clientId = process.env.azureServicePrincipalClientId;
     const secret = process.env.azureServicePrincipalPassword;
@@ -40,11 +40,11 @@ export class AzureLoginService extends BaseService {
   }
 
   public async interactiveLogin(options?: InteractiveLoginOptions): Promise<AuthResponse> {
-    let authResp: AuthResponse = {credentials: undefined, subscriptions: []};
+    let authResp: AuthResponse = { credentials: undefined, subscriptions: [] };
     const fileTokenCache = new SimpleFileTokenCache();
-    if(fileTokenCache.isEmpty()){
+    if (fileTokenCache.isEmpty()) {
       await open("https://microsoft.com/devicelogin");
-      authResp = await interactiveLoginWithAuthResponse({...options, tokenCache: fileTokenCache});
+      authResp = await interactiveLoginWithAuthResponse({ ...options, tokenCache: fileTokenCache });
       fileTokenCache.addSubs(authResp.subscriptions);
     } else {
       authResp.credentials = new DeviceTokenCredentials(undefined, undefined, fileTokenCache.first().userId, undefined, undefined, fileTokenCache);
@@ -52,10 +52,9 @@ export class AzureLoginService extends BaseService {
     }
 
     return authResp;
-
   }
 
   public async servicePrincipalLogin(clientId: string, secret: string, tenantId: string, options: AzureTokenCredentialsOptions): Promise<AuthResponse> {
-    return loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
+    return await loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
   }
 }
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index 4e340cc..8ab9477 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -24,14 +24,14 @@ export class PackageService extends BaseService {
   /**
    * Creates the function.json binding files required for the serverless service
    */
-  public createBindings(): Promise<any[]> {
+  public async createBindings(): Promise<void> {
     const createEventsPromises = this.serverless.service.getAllFunctions()
       .map((functionName) => {
         const metaData = Utils.getFunctionMetaData(functionName, this.serverless);
         return this.createBinding(functionName, metaData);
       });
 
-    return Promise.all(createEventsPromises);
+    await Promise.all(createEventsPromises);
   }
 
   /**
@@ -95,10 +95,12 @@ export class PackageService extends BaseService {
     functionJSON.scriptFile = functionMetadata.handlerPath;
     const functionObject = this.slsFunctions()[functionName];
     const bindingAzureSettings = Utils.getIncomingBindingConfig(functionObject)["x-azure-settings"];
+
     if (bindingAzureSettings.route) {
       // Find incoming binding within functionJSON and set the route
       const index = (functionJSON.bindings as any[])
         .findIndex((binding) => (!binding.direction || binding.direction === "in"));
+
       functionJSON.bindings[index].route = bindingAzureSettings.route;
     }
 
@@ -108,7 +110,6 @@ export class PackageService extends BaseService {
     }
 
     const functionJsonString = JSON.stringify(functionJSON, null, 2);
-
     fs.writeFileSync(path.join(functionDirPath, "function.json"), functionJsonString);
 
     return Promise.resolve();


Test patch:
diff --git a/src/armTemplates/resources/storageAccount.test.ts b/src/armTemplates/resources/storageAccount.test.ts
index 7ee5cae..edc523d 100644
--- a/src/armTemplates/resources/storageAccount.test.ts
+++ b/src/armTemplates/resources/storageAccount.test.ts
@@ -10,7 +10,9 @@ describe("Storage Account Resource", () => {
   const config: ServerlessAzureConfig = {
     functions: [],
     plugins: [],
+    package: null,
     provider: {
+      runtime: "nodejs10.x",
       prefix: "sls",
       name: "azure",
       region: "westus",
diff --git a/src/index.test.ts b/src/index.test.ts
index 76e999c..c54e7ab 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -5,8 +5,7 @@ import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
 import { AzureLoginPlugin } from "./plugins/login/azureLoginPlugin";
-import { AzureApimServicePlugin } from "./plugins/apim/azureApimServicePlugin";
-import { AzureApimFunctionPlugin } from "./plugins/apim/azureApimFunctionPlugin";
+import { AzureApimPlugin } from "./plugins/apim/azureApimPlugin";
 import AzureProvider from "./provider/azureProvider";
 import { AzureFuncPlugin } from "./plugins/func/azureFuncPlugin";
 import { AzureOfflinePlugin } from "./plugins/offline/azureOfflinePlugin";
@@ -30,8 +29,7 @@ describe("Azure Index", () => {
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureRemovePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureLoginPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureDeployPlugin);
-    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimServicePlugin);
-    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimFunctionPlugin);
+    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureFuncPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureOfflinePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureRollbackPlugin);
diff --git a/src/plugins/apim/azureApimFunctionPlugin.test.ts b/src/plugins/apim/azureApimFunctionPlugin.test.ts
deleted file mode 100644
index 0e81528..0000000
--- a/src/plugins/apim/azureApimFunctionPlugin.test.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { MockFactory } from "../../test/mockFactory";
-import { invokeHook } from "../../test/utils";
-import { AzureApimFunctionPlugin } from "./azureApimFunctionPlugin";
-
-jest.mock("../../services/apimService");
-import { ApimService } from "../../services/apimService";
-
-describe("APIM Function Plugin", () => {
-  it("calls deploy function", async () => {
-    const deployFunction = jest.fn();
-
-    ApimService.prototype.deployFunction = deployFunction;
-
-    const sls = MockFactory.createTestServerless();
-    sls.service.provider["apim"] = "apim config"
-    const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimFunctionPlugin(sls, options);
-
-    await invokeHook(plugin, "after:deploy:function:deploy");
-
-    expect(sls.cli.log).toBeCalledWith("Starting APIM function deployment")
-    expect(deployFunction).toBeCalled();
-    expect(sls.cli.log).lastCalledWith("Finished APIM function deployment")
-  });
-});
diff --git a/src/plugins/apim/azureApimServicePlugin.test.ts b/src/plugins/apim/azureApimPlugin.test.ts
similarity index 58%
rename from src/plugins/apim/azureApimServicePlugin.test.ts
rename to src/plugins/apim/azureApimPlugin.test.ts
index b748395..3ff6cec 100644
--- a/src/plugins/apim/azureApimServicePlugin.test.ts
+++ b/src/plugins/apim/azureApimPlugin.test.ts
@@ -1,14 +1,14 @@
 import Serverless from "serverless";
 import { MockFactory } from "../../test/mockFactory";
 import { invokeHook } from "../../test/utils";
-import { AzureApimServicePlugin } from "./azureApimServicePlugin";
+import { AzureApimPlugin } from "./azureApimPlugin";
 
 jest.mock("../../services/apimService");
 import { ApimService } from "../../services/apimService";
 
 describe("APIM Service Plugin", () => {
   it("is defined", () => {
-    expect(AzureApimServicePlugin).toBeDefined();
+    expect(AzureApimPlugin).toBeDefined();
   });
 
   it("can be instantiated", () => {
@@ -17,46 +17,40 @@ describe("APIM Service Plugin", () => {
       stage: "",
       region: "",
     }
-    const plugin = new AzureApimServicePlugin(serverless, options);
+    const plugin = new AzureApimPlugin(serverless, options);
 
     expect(plugin).not.toBeNull();
   });
 
-  it("calls deploy API and deploy functions", async () => {
-    const deployApi = jest.fn();
-    const deployFunctions = jest.fn();
+  it("calls APIM service deploy", async () => {
+    const deploy = jest.fn();
 
-    ApimService.prototype.deployApi = deployApi;
-    ApimService.prototype.deployFunctions = deployFunctions;
+    ApimService.prototype.deploy = deploy;
 
     const sls = MockFactory.createTestServerless();
     sls.service.provider["apim"] = "apim config"
     const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimServicePlugin(sls, options);
+    const plugin = new AzureApimPlugin(sls, options);
 
     await invokeHook(plugin, "after:deploy:deploy");
 
     expect(sls.cli.log).toBeCalledWith("Starting APIM service deployment")
-    expect(deployApi).toBeCalled();
-    expect(deployFunctions).toBeCalled();
+    expect(deploy).toBeCalled();
     expect(sls.cli.log).lastCalledWith("Finished APIM service deployment")
   });
 
   it("does not call deploy API or deploy functions when \"apim\" not included in config", async () => {
-    const deployApi = jest.fn();
-    const deployFunctions = jest.fn();
+    const deploy = jest.fn();
 
-    ApimService.prototype.deployApi = deployApi;
-    ApimService.prototype.deployFunctions = deployFunctions;
+    ApimService.prototype.deploy = deploy;
 
     const sls = MockFactory.createTestServerless();
     const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimServicePlugin(sls, options);
+    const plugin = new AzureApimPlugin(sls, options);
 
     await invokeHook(plugin, "after:deploy:deploy");
 
     expect(sls.cli.log).not.toBeCalled()
-    expect(deployApi).not.toBeCalled();
-    expect(deployFunctions).not.toBeCalled();
+    expect(deploy).not.toBeCalled();
   });
 });
diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts
index 208670c..dbf2bc8 100644
--- a/src/plugins/deploy/azureDeployPlugin.test.ts
+++ b/src/plugins/deploy/azureDeployPlugin.test.ts
@@ -1,6 +1,6 @@
 import { Site } from "@azure/arm-appservice/esm/models";
 import Serverless from "serverless";
-import { ServerlessAzureOptions } from "../../models/serverless";
+import { ServerlessAzureOptions, ServerlessAzureConfig } from "../../models/serverless";
 import { MockFactory } from "../../test/mockFactory";
 import { invokeHook } from "../../test/utils";
 import { AzureDeployPlugin } from "./azureDeployPlugin";
@@ -11,6 +11,7 @@ import { FunctionAppService } from "../../services/functionAppService";
 
 jest.mock("../../services/resourceService");
 import { ResourceService } from "../../services/resourceService";
+import { ApimService } from "../../services/apimService";
 
 describe("Deploy plugin", () => {
   let sls: Serverless;
@@ -25,6 +26,7 @@ describe("Deploy plugin", () => {
 
   beforeEach(() => {
     FunctionAppService.prototype.getFunctionZipFile = jest.fn(() => "serviceName.zip");
+    ApimService.prototype.deploy = jest.fn();
 
     sls = MockFactory.createTestServerless();
     options = MockFactory.createTestServerlessOptions();
@@ -64,7 +66,7 @@ describe("Deploy plugin", () => {
         "Azure Functions are zipped up as a package and deployed together as a unit");
   });
 
-  it("lists deployments", async () => {
+  it("lists deployments from sub-command", async () => {
     const deploymentString = "deployments";
     ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deploymentString));
     await invokeHook(plugin, "deploy:list:list");
@@ -72,7 +74,21 @@ describe("Deploy plugin", () => {
     expect(sls.cli.log).lastCalledWith(deploymentString);
   });
 
-  it("Crashes deploy list if function is specified", async () => {
+  it("deploys APIM from sub-command if configured", async () => {
+    (sls.service as any as ServerlessAzureConfig).provider.apim = {} as any;
+    plugin = new AzureDeployPlugin(sls, {} as any);
+    await invokeHook(plugin, "deploy:apim:apim");
+    expect(ApimService.prototype.deploy).toBeCalled();
+  });
+
+  it("skips deployment of APIM from sub-command if not configured", async () => {
+    delete (sls.service as any as ServerlessAzureConfig).provider.apim;
+    plugin = new AzureDeployPlugin(sls, {} as any);
+    await invokeHook(plugin, "deploy:apim:apim");
+    expect(ApimService.prototype.deploy).not.toBeCalled();
+  });
+
+  it("crashes deploy list if function is specified", async () => {
     plugin = new AzureDeployPlugin(sls, { function: "myFunction" } as any);
     await expect(invokeHook(plugin, "deploy:list:list"))
       .rejects.toThrow("The Azure Functions plugin does not currently support deployments of individual functions. " +
diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts
index 4711370..52665b8 100644
--- a/src/services/apimService.test.ts
+++ b/src/services/apimService.test.ts
@@ -10,7 +10,6 @@ import apimGetService200 from "../test/responses/apim-get-service-200.json";
 import apimGetApi200 from "../test/responses/apim-get-api-200.json";
 import apimGetApi404 from "../test/responses/apim-get-api-404.json";
 import { FunctionAppService } from "./functionAppService";
-import { Site } from "@azure/arm-appservice/esm/models";
 import {
   PropertyContract, BackendContract, BackendCreateOrUpdateResponse,
   ApiCreateOrUpdateResponse, PropertyCreateOrUpdateResponse, ApiContract,
@@ -20,12 +19,22 @@ import {
   ApiPolicyCreateOrUpdateResponse,
 } from "@azure/arm-apimanagement/esm/models";
 import { AzureNamingService } from "./namingService";
+import { ApiManagementConfig } from "../models/apiManagement";
 
 describe("APIM Service", () => {
-  const apimConfig = MockFactory.createTestApimConfig();
+  let apimConfig: ApiManagementConfig;
+
+  const functionApp = {
+    id: "/testapp1",
+    name: "Test Site",
+    location: "West US",
+    defaultHostName: "testsite.azurewebsites.net",
+  };
+
   let serverless: Serverless;
 
   beforeEach(() => {
+    apimConfig = MockFactory.createTestApimConfig();
     const slsConfig: any = {
       ...MockFactory.createTestService(MockFactory.createTestSlsFunctionConfig()),
       service: "test-sls",
@@ -114,7 +123,7 @@ describe("APIM Service", () => {
       axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, apimGetApi404, 404));
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi("unknown");
       expect(api).toBeNull();
     });
 
@@ -122,12 +131,14 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"] = null;
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi("unknown");
 
       expect(api).toBeNull();
     });
 
     it("returns the API reference", async () => {
+      const defaultApi = apimConfig.apis[0];
+
       const expectedResponse = interpolateJson(apimGetApi200, {
         resourceGroup: {
           name: serverless.service.provider["resourceGroup"],
@@ -137,23 +148,23 @@ describe("APIM Service", () => {
           name: apimConfig.name,
         },
         resource: {
-          name: apimConfig.api.name,
-          displayName: apimConfig.api.displayName,
-          description: apimConfig.api.description,
-          path: apimConfig.api.path,
+          name: defaultApi.name,
+          displayName: defaultApi.displayName,
+          description: defaultApi.description,
+          path: defaultApi.path,
         },
       });
 
       axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, expectedResponse));
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi(defaultApi.name);
 
       expect(api).not.toBeNull();
       expect(api).toMatchObject({
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
+        displayName: defaultApi.displayName,
+        description: defaultApi.description,
+        path: defaultApi.path,
       });
     });
   });
@@ -165,7 +176,6 @@ describe("APIM Service", () => {
     let serviceName: string;
     let apiName: string;
     let backendName: string;
-    let functionApp: Site;
     let masterKey: string;
     let expectedApi: ApiContract;
     let expectedApiResult: ApiContract;
@@ -173,19 +183,13 @@ describe("APIM Service", () => {
     let expectedProperty: PropertyContract;
 
     beforeEach(() => {
-      backendConfig = apimConfig.backend || {} as BackendContract;
+      backendConfig = apimConfig.backends[0] || {} as BackendContract;
       resourceGroupName = serverless.service.provider["resourceGroup"];
       appName = serverless.service["service"];
       serviceName = apimConfig.name;
-      apiName = apimConfig.api.name;
-      backendName = backendConfig.name || appName;
+      apiName = apimConfig.apis[0].name;
+      backendName = backendConfig[0] ? backendConfig[0].name : `${appName}-backend`;
 
-      functionApp = {
-        id: "/testapp1",
-        name: "Test Site",
-        location: "West US",
-        defaultHostName: "testsite.azurewebsites.net",
-      };
       masterKey = "ABC123";
 
       FunctionAppService.prototype.get = jest.fn(() => Promise.resolve(functionApp));
@@ -193,22 +197,23 @@ describe("APIM Service", () => {
 
       expectedApi = {
         isCurrent: true,
-        subscriptionRequired: apimConfig.api.subscriptionRequired,
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
-        protocols: apimConfig.api.protocols,
+        name: apimConfig.apis[0].name,
+        subscriptionRequired: apimConfig.apis[0].subscriptionRequired,
+        displayName: apimConfig.apis[0].displayName,
+        description: apimConfig.apis[0].description,
+        path: apimConfig.apis[0].path,
+        protocols: apimConfig.apis[0].protocols,
       };
 
       expectedApiResult = {
-        id: apimConfig.api.name,
-        name: apimConfig.api.name,
+        id: apimConfig.apis[0].name,
+        name: apimConfig.apis[0].name,
         isCurrent: true,
-        subscriptionRequired: apimConfig.api.subscriptionRequired,
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
-        protocols: apimConfig.api.protocols,
+        subscriptionRequired: apimConfig.apis[0].subscriptionRequired,
+        displayName: apimConfig.apis[0].displayName,
+        description: apimConfig.apis[0].description,
+        path: apimConfig.apis[0].path,
+        protocols: apimConfig.apis[0].protocols,
       };
 
       expectedBackend = {
@@ -217,10 +222,11 @@ describe("APIM Service", () => {
             "x-functions-key": [`{{${serverless.service["service"]}-key}}`],
           },
         },
+        name: backendConfig[0] ? backendConfig[0].name : `${appName}-backend`,
         title: backendConfig.title || functionApp.name,
         tls: backendConfig.tls,
         proxy: backendConfig.proxy,
-        description: backendConfig.description,
+        description: backendConfig.description || "Function App Backend",
         protocol: backendConfig.protocol || "http",
         resourceId: `https://management.azure.com${functionApp.id}`,
         url: `https://${functionApp.defaultHostName}/api`,
@@ -243,9 +249,8 @@ describe("APIM Service", () => {
       ApiPolicy.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null));
 
       const apimService = new ApimService(serverless);
-      const result = await apimService.deployApi();
 
-      expect(result).toMatchObject(expectedApiResult);
+      await expect(apimService.deploy()).resolves.not.toBeNull();
       expect(Api.prototype.createOrUpdate).toBeCalledWith(
         resourceGroupName,
         serviceName,
@@ -285,7 +290,7 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"]["cors"] = corsPolicy;
 
       const apimService = new ApimService(serverless);
-      const result = await apimService.deployApi();
+      const result = await apimService.deploy();
 
       expect(result).not.toBeNull();
       expect(ApiPolicy.prototype.createOrUpdate).toBeCalledWith(
@@ -303,7 +308,7 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"] = null;
 
       const service = new ApimService(serverless);
-      const api = await service.deployApi();
+      const api = await service.deploy();
 
       expect(api).toBeNull();
     });
@@ -313,7 +318,7 @@ describe("APIM Service", () => {
       Api.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
     });
 
     it("fails when Backend deployment fails", async () => {
@@ -324,7 +329,7 @@ describe("APIM Service", () => {
       Backend.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
     });
 
     it("fails when Property deployment fails", async () => {
@@ -337,7 +342,113 @@ describe("APIM Service", () => {
       Property.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
+    });
+
+    it("automatically creates API and Backend if not explicitly defined", async () => {
+      apimConfig.apis = [];
+      apimConfig.backends = [];
+
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(Api.prototype.createOrUpdate).toBeCalled();
+      expect(Backend.prototype.createOrUpdate).toBeCalled();
+    });
+
+    it("creates multiple API's & Backends", async () => {
+      apimConfig.apis = MockFactory.createTestApimApis(3);
+      apimConfig.backends = MockFactory.createTestApimBackends(3);
+
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(Api.prototype.createOrUpdate).toBeCalledTimes(apimConfig.apis.length);
+      expect(Backend.prototype.createOrUpdate).toBeCalledTimes(apimConfig.apis.length);
+    });
+
+    it("infers APIM operation configuration from HTTP binding", async () => {
+      const functions = MockFactory.createTestSlsFunctionConfig();
+      Object.assign(serverless.service, { functions });
+
+      let apimResource: ApiManagementServiceResource = {
+        name: apimConfig.name,
+        location: "West US",
+        gatewayUrl: "https://you.url.com",
+        publisherEmail: "someone@example.com",
+        publisherName: "Someone",
+        sku: {
+          capacity: 1,
+          name: "Consumption",
+        },
+      };
+
+      ApiManagementService.prototype.get =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiManagementServiceGetResponse>(apimResource, 200));
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiOperation.prototype.createOrUpdate =
+        jest.fn((resourceGroup, serviceName, apiName, operationName, operationContract) => {
+          const response = MockFactory.createTestArmSdkResponse<ApiOperationCreateOrUpdateResponse>(operationContract, 201);
+          return Promise.resolve(response);
+        });
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledTimes(2)
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledWith(
+        resourceGroupName,
+        serviceName,
+        apiName,
+        "hello",
+        {
+          displayName: "hello",
+          description: "",
+          method: "get",
+          urlTemplate: "hello",
+          templateParameters: [],
+          responses: [],
+        }
+      );
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledWith(
+        resourceGroupName,
+        serviceName,
+        apiName,
+        "goodbye",
+        {
+          displayName: "goodbye",
+          description: "",
+          method: "get",
+          urlTemplate: "goodbye",
+          templateParameters: [],
+          responses: [],
+        }
+      );
     });
   });
 
@@ -349,9 +460,7 @@ describe("APIM Service", () => {
       const deploySpy = jest.spyOn(apimService, "deployFunction");
 
       const serviceResource: ApiManagementServiceResource = MockFactory.createTestApimService();
-      const api: ApiContract = MockFactory.createTestApimApi();
-
-      await apimService.deployFunctions(serviceResource, api);
+      await apimService.deployFunctions(functionApp, serviceResource);
 
       expect(deploySpy).not.toBeCalled();
     });
@@ -359,7 +468,7 @@ describe("APIM Service", () => {
     it("ensures all serverless functions have been deployed into specified API", async () => {
       const slsFunctions = _.values(serverless.service["functions"]);
 
-      const apimResource: ApiManagementServiceResource = {
+      let apimResource: ApiManagementServiceResource = {
         name: apimConfig.name,
         location: "West US",
         publisherEmail: "someone@example.com",
@@ -370,9 +479,9 @@ describe("APIM Service", () => {
         },
       };
 
-      const apiContract = apimConfig.api;
+      const apiContracts = apimConfig.apis;
       ApiManagementService.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiManagementServiceGetResponse>(apimResource, 200));
-      Api.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiGetResponse>(apiContract, 200));
+      Api.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiGetResponse>(apiContracts[0], 200));
 
       ApiOperation.prototype.createOrUpdate =
         jest.fn((resourceGroup, serviceName, apiName, operationName, operationContract) => {
@@ -385,11 +494,12 @@ describe("APIM Service", () => {
       const deployFunctionSpy = jest.spyOn(ApimService.prototype, "deployFunction");
 
       const service = new ApimService(serverless);
-      const apimInstance = await service.get();
-      const api = await service.getApi();
+      apimResource = await service.get();
+      const api = await service.getApi(apimConfig.apis[0].name);
 
-      await service.deployFunctions(apimInstance, api);
+      await service.deploy();
 
+      expect(api).not.toBeNull();
       expect(deployFunctionSpy).toBeCalledTimes(slsFunctions.length);
 
       const createOperationCall = ApiOperation.prototype.createOrUpdate as jest.Mock;
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index c02d327..ad81fb9 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -1,4 +1,4 @@
-import { ApiContract, ApiManagementServiceResource } from "@azure/arm-apimanagement/esm/models";
+import { ApiContract, ApiManagementServiceResource, BackendContract } from "@azure/arm-apimanagement/esm/models";
 import { FunctionEnvelope, Site } from "@azure/arm-appservice/esm/models";
 import { DeploymentExtended, DeploymentsListByResourceGroupResponse } from "@azure/arm-resources/esm/models";
 import { HttpHeaders, HttpOperationResponse, HttpResponse, WebResource } from "@azure/ms-rest-js";
@@ -36,7 +36,7 @@ export class MockFactory {
     sls.config.servicePath = "";
     sls.setProvider = jest.fn();
     sls["processedInput"] = {
-      commands: [ ServerlessCliCommand.DEPLOY ],
+      commands: [ServerlessCliCommand.DEPLOY],
       options: {}
     };
     return sls;
@@ -184,13 +184,14 @@ export class MockFactory {
   }
 
   public static createTestParameters(wrap = true) {
-    return (wrap) ? {
-      param1: { value: "1", type: "String" },
-      param2: { value: "2", type: "String" },
-    } : {
-      param1: "1",
-      param2: "2",
-    }
+    return (wrap)
+      ? {
+        param1: { value: "1", type: "String" },
+        param2: { value: "2", type: "String" },
+      } : {
+        param1: "1",
+        param2: "2",
+      }
   }
 
   public static createTestDeployment(name?: string, second: number = 0): DeploymentExtended {
@@ -298,14 +299,15 @@ export class MockFactory {
   public static createTestApimConfig(generateName: boolean = false): ApiManagementConfig {
     return {
       name: generateName ? null : "test-apim-resource",
-      api: {
+      apis: [{
         name: "test-apim-api1",
         subscriptionRequired: false,
         displayName: "API 1",
         description: "description of api 1",
         protocols: ["https"],
         path: "test-api1",
-      },
+      }],
+      backends: [],
     };
   }
 
@@ -521,10 +523,42 @@ export class MockFactory {
     };
   }
 
-  public static createTestApimApi(): ApiContract {
+  public static createTestApimApis(count: number): ApiContract[] {
+    const apis: ApiContract[] = [];
+    for (let i = 1; i <= count; i++) {
+      const api = MockFactory.createTestApimApi(i);
+      apis.push(api);
+    }
+
+    return apis;
+  }
+
+  public static createTestApimApi(index: number = 1): ApiContract {
+    return {
+      displayName: `API ${index}`,
+      description: `Description for API ${index}`,
+      name: `Api${index}`,
+      path: `/api${index}`,
+    };
+  }
+
+  public static createTestApimBackends(count: number): BackendContract[] {
+    const backends: BackendContract[] = [];
+    for (let i = 1; i <= count; i++) {
+      const backend = MockFactory.createTestApimBackend(i);
+      backends.push(backend);
+    }
+
+    return backends;
+  }
+
+  public static createTestApimBackend(index: number = 1): BackendContract {
     return {
-      name: "Api1",
-      path: "/api1",
+      name: `backend-${index}`,
+      description: `Description for Backend ${index}`,
+      title: `Backend ${index}`,
+      url: `/backend${index}`,
+      protocol: "http",
     };
   }