Defining API Gateway Endpoints
This lesson references the code in the aws-connectedcar-dotnet-serverless repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.
We’ll soon dive into the code-level details for defining and securing API endpoints. But let’s first outline how this is done, covering the use of both SAM and OpenAPI.
Endpoint Component Parts
To start, let’s break down the component parts of API Gateway endpoints. First, you have the API instance, within which you define a set of nested resource paths. Any of these resource paths can be turned into a working endpoint by adding optional path or query parameters and specifying HTTP methods (e.g. GET, POST, PUT, DELETE). Each endpoint and HTTP method combination then has an integration to a target URL or to an AWS service like Lambda. Lastly, the API instance also contains one or more “stages” to which API endpoints and their integrations can be deployed (more on this shortly).
This screenshot of the Admin API and its resources, in the console, shows how you can view these component parts. The nested resource paths are shown on the second panel on the left. The “Method Request” and “Method Response” panels on the right lead you to the endpoint configurations, while the “Integration Request” and "Integration Response" panels on the right lead you to the integration settings:
We’re not covering security mechanisms in this lesson, but we should note that these are defined at the API level and can then be applied selectively to individual endpoints and their integrations.
Deploying Full Stacks
As we touched on above, API Gateway has the concept of a “stage”. It’s possible to add stages that correspond to the development lifecycle, with names like “dev”, “test”, and “prod”. When built this way, one API instance can theoretically be used to host multiple system environments. However, we’re not using the staging feature in this way because it couples environments together, limiting their deployment options. Instead, we’re following what’s called the “full stack” approach. With this, environments are deployed entirely separately, with dedicated instances for each of all the component parts of the APIs and their downstream services.
Defining Endpoints with SAM
As we saw in the CloudFormation section, the Serverless Application Model (SAM) defines a set of resource types that are specifically tailored for API Gateway and integrations with Lambda. It’s possible to define APIs and backend services without it, but to do so you have to write a lot more template code. So the sample code for this course makes extensive use of SAM templates.
You can find the documentation for SAM here: https://aws.amazon.com/serverless/sam/
With a SAM template you first define an API resource, followed by a series of associated Lambdas. Each of these Lambdas then has, along with its own properties, an Event element that specifies an HTTP method plus a resource path and associated parameters in the API. Although the integration target (the Lambda) in this model is defined first, and then the endpoint, what you end up with in effect are a series of API endpoints that have integrations with Lambdas.
To illustrate this with sample code, let’s start with this example API resource, from the admin.yaml template for the SAM version of the .Net sample code:
The above API resource includes an Auth element for the API keys security mechanism. The corresponding customer.yaml template shows what this Auth element looks like when the API is secured using AWS Cognito:
Switching back to the admin.yaml template, here’s the CreateDealer Lambda that’s associated with the HTTP POST event for the “/admin/dealers” resource path:
And that’s all it takes to define an API with Lambdas and their associated event mappings using SAM. As you can see, this is a very concise way to define API endpoints, which we must emphasize does not include data schemas or request validation. Whatever request data is submitted to the API, of whatever size and form, gets proxied through to the target Lambdas. Given this limitation, this approach is best suited either to rapid prototyping or to cases where you also control the code for client applications.
Defining Endpoints with OpenAPI
To define more robust APIs that include data schemas and API-level request validation, the best approach is to define your endpoints with OpenAPI. Originally a product by SmartBear Software called Swagger, the OpenAPI Specification is now an open standard. Using a few AWS-specific extensions (which you’ll see in the code tutorials) it’s possible to use OpenAPI to define API Gateway data models, endpoints, and their integrations.
You can find the documentation for OpenAPI here: https://openapis.org
Looking at the sample code again, you can see that we still use part of the SAM format when defining APIs and Lambdas with OpenAPI. The Lambdas are defined using SAM, but the endpoints and their target integrations are defined in the referenced OpenAPI document instead. To illustrate, here’s the admin.yaml template for the OpenAPI deployment of the equivalent Admin API resource we saw above:
In the OpenAPI version of this template, the API resource still includes the “Auth” element for the API keys as well as the monitoring elements. Now, however, it also includes a “DefinitionBody” element that references an OpenAPI document that’s stored on an S3 bucket. At build time CloudFormation will include the OpenAPI document in-line with the rest of this template.
Below you can see the CreateDealer Lambda resource from this template, and you’ll note that the event mapping and reference to the API is no longer included:
We’ll cover the OpenAPI specification in more depth in the code tutorials, but let’s briefly highlight some examples here. Below, you can see the definition for the “create dealer” endpoint from the admin.openapi.yaml specification:
Note that the resource paths and HTTP method are defined here, along with a reference to the schema for the request body. We can see the definition for the "Dealer" data schema for this request body in the schemas.openapi.yaml specification:
If you look at the “update customer” endpoint in the customer.openapi.yaml specification, you can see the security element that references the CognitoAuthorizer:
This authorizer is declared in the "securitySchemes" section of this OpenAPI specification, and in this case replaces the Auth definition for the API found in the SAM version of the customer.yaml template:
We noted above that SAM, due to its lack of request validation, is best suited for prototyping or for use by in-house client applications. With OpenAPI, however, you can define data schemas with strict field-level constraints. By applying data schemas like this to the endpoints for requests bodies and parameters, it's possible to deploy hardened APIs that you can safely expose to third-party client applications.