0%
view

Building efficient APIs and infrastructure from one code base with Winglang

March 4, 2024
By
Ruslan
7
min read
Share article
There are various ways to build secure and efficient APIs today, each with its own benefits. However, how efficient and developer-friendly is your preferred method for building APIs?

In this article, we will investigate a new technology and construct a small API endpoint to address a corporate issue of connectivity between two services.

The task: Trigger GitHub Actions from BitBucket

In a corporate environment, there’s often a challenge in communication between two different systems. Our client organization is not an exception and as they were migrating the codebase from Bitbucket to GitHub they faced a challenge to make these two services talk to each other. They wanted a simple communication: once there is a change in the code in Bitbucket, trigger the pipeline in GitHub. That was it.

There was no easy solution to this problem:

  • Standard BitBucket Webhook feature can only initiate GET calls, whereas GitHub Actions APIs provide a POST endpoint to create a workflow dispatch event.
  • Third-party webhook integrations, such as Bitbucket Post Webhook, may take a long time to get approval in a corporate environment, but could ultimately provide a long-term solution.

The solution: Build an intermediate REST API endpoint

As Bitbucket Webhook could only call a GET endpoint a decision was made to create a small temporary endpoint in a private corporate network that will trigger a GitHub Actions API POST endpoint.

AWS infrastructure was a given and API Gateway seemed an appropriate service.

We looked at the following options:

  • Using CloudFormation seemed too much an investment for a temporary task.
  • Continue building in the existing Terraform stack seemed an overkill for this task as well.
  • Cloud Development Kit (CDK) seemed to be an option.
  • Winglang, something we have just heard by then, seemed both too good to be true and too much of an abstraction.

After struggling for a few days with CDK, we decided to give Winglang a go and were quite surprised how efficiently it served our use case.

Winglang

Wing is a general-purpose programming language, designed to develop entire cloud applications, including their infrastructure and application code. It is an attempt to combine infrastructure and runtime code into a single language which should give developers the ability to control all aspects of backend service development and its infrastructure in one codebase.

At the time of writing, AWS support was fully implemented, while other platforms such as Azure and GCP were supported partially.

Install Winglang

Run an NPM command to install a Wing CLI tool.

npm install -g winglang

Note, as of the time this was written, the requirements to run the latest version of Wing CLI (0.58.16) include Node.js version 20 or higher.

Create a new project

Run Wing CLI command to create a new sample project.

wing new empty

There is an experimental TypeScript support. If you want to try this, add --language ts option:

wing new empty --language ts

The result of the command is a sample project that includes Winglang code in main.wing as well as standard JS project configuration in a package.json.

wing-api/
├── main.w
├── package-lock.json
└── package.json

Use Wing commands for your dev workflow

Update the package.json file with the project workflow commands: compile, init, plan, apply in the scripts section.

{
  "name": "winglang-aws-api-gateway-sample",
  "version": "1.0.0",
  "description": "Winglang AWS API Gateway sample",
  "author": "Ruslan Kazakov",
  "license": "MIT",
  "wing": true,
  "scripts": {
    "clean": "rm -rf target",
    "compile": "wing compile --platform tf-aws main.w",
    "init": "cd target/main.tfaws && terraform init",
    "plan": "cd target/main.tfaws && terraform plan",
    "apply": "cd target/main.tfaws && terraform apply"
  }
}

Compile to Terraform

Winglang uses Terraform to orchestrate cloud infrastructure, so the first step is to compile Winglang code into Terraform code. If you need to set up Terraform on your machine, please refer to the Terraform installation article.

Note, that we are using tf-aws platform as we are using AWS as our cloud provider.

wing compile --platform tf-aws main.w
# OR
npm run compile

After the compile command runs without any issues, it generates a new target folder. This folder contains the Terraform code for the infrastructure and the JavaScript code for the application logic.

Every time you modify Winglang code, you should run the compile command.

Initialise your Terraform project

The next step is to initialise the Terraform configuration by executing init command.

cd target/main.tfaws && terraform init
# OR
npm run init

This command performs several different initialisation steps in order to prepare the current working directory for use with Terraform.

Preview your infrastructure

The terraform plan command creates an execution plan, which lets you preview the changes that Terraform plans to make to your infrastructure.

cd target/main.tfaws && terraform plan
# OR
npm run plan

This command does not execute the proposed changes. Instead, it allows you to validate whether the suggested modifications meet your expectations before applying them.

Deploy your code to the cloud

Finally, we can deploy our changes to the cloud by running the apply command.

cd target/main.tfaws && terraform apply
# OR
npm run apply

Build your API

To solve the missing piece in our connectivity problem, we will build a REST endpoint that will trigger GitHub Actions pipeline.

We updated main.w file to listen for a GET HTTP request and perform a PORT HTTP request to GitHub API in its handler function.

bring cloud;
bring http;

let api = new cloud.Api();

let host = "";
let owner = "bear-plus";
let repo = "codeceptjs-playwright-typescript-boilerplate";
let workflow_id = "e2e-test-automation.yml";
let endpoint = "repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches";

api.get("/trigger", inflight (req: cloud.ApiRequest): cloud.ApiResponse => {
 http.post("{host}/{endpoint}", {
    headers: {
      "Authorization": "Bearer github_pat_123_ABC",
      "Accept": "application/vnd.github+json",
      "X-GitHub-Api-Version": "2022-11-28",
    },
    body: Json.stringify({ "ref": "main" }),
  });
  return cloud.ApiResponse {
    status: 200,
    body: "GitHub API is triggered.",
  };
});

In a single Winglang file, it’s possible to combine not only the configuration for the infrastructure, but also the code that governs the logic of the application. This means that all the necessary components for our project can be consolidated and managed in one place, making it a more efficient and streamlined process.

Manage secrets

We need a security token to trigger the GitHub API. Since a security token is a sensitive information, we’ll store it as a secret.

Winglang provides a special class, cloud.secret, for working with sensitive data in the cloud. Storing a secret enables you to use its value across various compute tasks, while only needing to update or revoke it in a single location.

let githubToken = new cloud.Secret(
  name: "github-token",
);

let token = githubToken.value();

We will use AWS CLI to create a secret in AWS Secrets Manager by executing the command below.

aws secretsmanager create-secret --name github-token --secret-string github_pat_123_ABC

To test our secret during development, we must create a secrets.json configuration file containing our secret.

{
  "github-token": "github_pat_123_ABC"
}

Make sure that secrets.json is located at ~/.wing/secrets.json on your local system.

Put everything together

The final solution will include some response handling and will use the secret we created.

bring cloud;
bring http;

let api = new cloud.Api();
let githubToken = new cloud.Secret(
  name: "github-token",
);
let host = "https://api.github.com";
let owner = "bear-plus";
let repo = "codeceptjs-playwright-typescript-boilerplate";
let workflow_id = "e2e-test-automation.yml";
let endpoint = "repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches";

api.get("/trigger", inflight (req: cloud.ApiRequest): cloud.ApiResponse => {
 let token = githubToken.value();
 
  let response = http.post("{host}/{endpoint}", {
    headers: {
      "Authorization": "Bearer {token}",
      "Accept": "application/vnd.github+json",
      "X-GitHub-Api-Version": "2022-11-28",
    },
    body: Json.stringify({ "ref": "main" }),
  });
  if response.status == 204 {
    return cloud.ApiResponse {
      status: 200,
      body: "E2E pipeline triggered successfully.",
    };
  }
  return cloud.ApiResponse {
    status: response.status,
    body: response.body,
  };
});

This short snippet will not only provision API Gateway instance and a Lambda function with JavaScript application code, but will also orchestrate the whole cloud infrastructure behind it:

  • IAM role and policy
  • VPC
  • VPC Endpoint
  • Elastic IP
  • Security Group
  • CloudWatch log group
  • S3 Bucket

Test your infrastructure and application locally

Our API endpoint is now ready for testing. Winglang provides the ability to simulate and visualize your infrastructure on your local machine using Wing Console, which can be served as a web application executed locally.

Wing Console inside VS Code

You can also run Wing Console by installing a VS Code extension and execute it directly in your development environment, alongside your code.

Wing Console settings panel

You can test and debug your APIs by sending requests right from the Wing Console in the cloud.Api settings panel. Once we are happy with our configuration and code, we can deploy it to our cloud provider.

Deploy your project to the cloud

Once all the testing has been completed locally, we can deploy our Winglang project to the cloud.

AWS Console shows API Gateway instance deployed

A new Gateway API instance has been created and its endpoint is available from API settings.

Conclusion

We were truly impressed by the solution we were able to implement using Winglang. The unique ability to provision infrastructure and simultaneously write the code for the backend logic within a single codebase makes it an extraordinarily powerful and efficient development approach. This not only streamlines the entire development process but also minimizes the potential for errors, thereby enhancing productivity and reducing the time to market.

minimizesMoreover, the added feature of being able to test and debug your project before deploying it to the cloud environment is a game-changer. It virtually eliminates the risk of deploying faulty code, saving considerable effort and resources. Additionally, it allows developers to troubleshoot and fix issues in a controlled environment, thus ensuring the stability and reliability of the final product.

Source code

The source code described in this article is hosted in Winglang AWS API Gateway and available in Bear Plus GitHub space.