Apexlang: Project Templates & Code Generation
Tags: typescript, deno, opensource, tooling
Published: 2023-01-06 18:02:14

> ### TL;DR:
> The `apex` CLI let you create projects from templates and the Apexlang IDL lets you generate code, documentation, schemas, and more from a single source. Quick links: [Github Repo](github.com/apexlang/apex) & [apexlang.io homepage](https://apexlang.io/)

How often do you start projects the same way? With the same boilerplate, same dependencies, same configuration, same everything? If you’re like me, you get your settings dialed in and reuse them in a base project template repeatedly. Eventually you might find yourself creating [git repos](https://github.com/jsoverson/typescript-boilerplate) or [scripts](https://twitter.com/jsoverson/status/1097881605824237569) to make this process buttery smooth.

Tools like [`yeoman`](https://yeoman.io/), [`degit`](https://github.com/Rich-Harris/degit), and [`cargo`](https://github.com/cargo-generate/cargo-generate) generate kept me happy for years. They add basic templating capabilities to the standard `git clone` but they stop there. You’ll be hard pressed to find tools that go beyond setting up a directory structure.

That’s where [Apexlang](https://apexlang.io) comes in. Apexlang is a code generation and templating tool suite. It started as an interface definition language (IDL) for WebAssembly called WIDL but was too useful to be hidden away. It now contains the base functionality of tools like `degit` combined with the code generation capability of tools like `protoc` and `smithy`. And unlike tools like `protoc` and `smithy`, code generators are written in TypeScript and are easier to get started with.

With Apexlang you get your boilerplate along with code, documentation, schemas, and more all generated automatically, continuously, and easily.

## Check it out

```sh
$ apex new git@github.com:apexlang/codegen.git -p templates/nodejs my-project
Cloning into '/var/folders/pg/h7s94pd90w54hcbjpkvm58240000gn/T/77f11564'...
remote: Enumerating objects: 256, done.
remote: Counting objects: 100% (256/256), done.
remote: Compressing objects: 100% (210/210), done.
remote: Total 256 (delta 54), reused 135 (delta 18), pack-reused 0
Receiving objects: 100% (256/256), 150.38 KiB | 6.27 MiB/s, done.
Resolving deltas: 100% (54/54), done.
 ? Please enter the project description ›
INFO Writing file my-project/package.json (mode:100644)
INFO Writing file my-project/apex.yaml (mode:100644)
INFO Writing file my-project/apex.axdl (mode:100644)
INFO Writing file my-project/.gitignore (mode:100644)
INFO Writing file my-project/tsconfig.json (mode:100644)
INFO Writing file my-project/.vscode/settings.json (mode:100644)
INFO Writing file my-project/.vscode/tasks.json (mode:100644
```

The `apex new` command clones a repository to use as a template. The `-p` option above tells apex to use a sub-directory and you can use `-b` to specify a branch if necessary. The final argument is the directory to create.

The template itself has configuration defined in .template files like [this one](https://github.com/apexlang/codegen/blob/main/templates/nodejs/.template). Any file `apex` finds with a `.tmpl` extension is treated as a text file to render with data defined in the `.template` configuration along with user or environment variables.

Cloning projects and rendering templates is useful, but it's the tip of the iceberg.

The example above is a bare-bones TypeScript & node.js template. It includes an `apex.axdl` definition and an `apex.yaml` configuration file. These files are what drives `apex` after the initial project creation.

This templates' yaml configuration is simple. It points to the Apexlang definition file (an `.axdl` file) and specifies the code generator plugin to use.

```yaml
spec: apex.axdl
plugins:
  - 'https://raw.githubusercontent.com/apexlang/codegen/main/src/typescript/plugin.ts'
```

Plugins dynamically generate further configuration based on the Apexlang definition. Yeah, `apex` uses Apexlang to configure `apex` configuration.

Plugins are TypeScript files that export a single function. The function takes in the current config along with an Apexlang spec, and spits out a new configuration.

The `axdl` file is where the magic is defined. It’s an interface definition language that looks a lot like GraphQL:

```graphql
namespace "greeting.v1"

interface Greeter {
  sayHello(to: Person): string
}

type Person {
  firstName: string
  lastName: string
}
```

It models the parts you need to describe most APIs for languages and services.

With a configuration and an `.axdl` file, we can start generating code:

```sh
$ apex generate
INFO Writing file ./src/api.ts (mode:644)
INFO Writing file ./src/interfaces.ts (mode:644)
apex generate creates two newfiles, api.ts and interfaces.ts.
```

`src/interfaces.ts` illustrates how `apex` translates Apexlang types to TypeScript code. Apexlang interfaces become TypeScript interfaces and Apexlang types become TypeScript classes.

```ts
export interface Greeter {
  sayHello(to: Person): string;
}

export class Person {
  firstName: string;
  lastName: string;
  constructor({
    firstName = "",
    lastName = ""
  }: { firstName?: string; lastName?: string } = {}) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
```

In `src/api.ts`, we get a boilerplate implementation of our interfaces that we can fill out with business logic. All the imports are handled for you and `apex` leaves you ready to start coding.

```ts
import { Greeter, Person } from "./interfaces";

class GreeterImpl implements Greeter {
  sayHello(to: Person): string {
    return "";
  }
}
```

We can also add documentation to our `.axdl` file and it will render as comments in the code.

```graphql
namespace "greeting.v1"

"A simple greeting service"
interface Greeter {
  "Say hello to a Person"
  sayHello(to: Person): string
}
"An instance of a person"
type Person {
  "The person's first name"
  firstName: string
  "The person's last name"
  lastName: string
}
apex generate now produces this:

// A simple greeting service
export interface Greeter {
  // Say hello to a Person
  sayHello(to: Person): string;
}

// An instance of a person
export class Person {
  // The person's first name
  firstName: string;
  // The person's last name
  lastName: string;
  [rest snipped...]
}
```

## More generators!

Writing what looks like code to generate a single instance of other code isn’t that exciting. You could have written the TypeScript by hand. But `apex` is more than just a code generator. It’s a code generation framework. It’s a templating framework. It’s a documentation framework. It’s a schema framework. It’s a configuration framework. It’s a framework for frameworks.

We can just as easily generate markdown documentation for our API by adding in a custom generates section. (These are what plugins generate for you but you can add your own manually.)

```yaml
generates:
  API.md:
    module: https://deno.land/x/apex_codegen/markdown/mod.ts
    config:
      title: 'My Awesome Project'
```

The generates block is keyed with the file to be generated (i.e. `API.md`) and the generator configuration. In this case we're delegating to the markdown module of the official [Apexlang codegen](https://github.com/apexlang/codegen) project.

This is the markdown we generate:

```md
# My Awesome Project

Namespace: **`greeting.v1`**

## Interfaces

### **Greeter**

A simple greeting service

- **`sayHello(to: Person) -> string`**: Say hello to a Person

## Types

### **Person**

An instance of a person

- **`firstName: string`** : The person's first name
- **`lastName: string`** : The person's last name
```

There are generators for C#, Go, TinyGo, Rust, Java, Protobuf, OpenAPI, JSONSchema, Python, [and more](https://github.com/apexlang/codegen/tree/main/src). If you work in shops with many teams and languages, you must look into Apexlang. It’s a game changer. Specify your interfaces once and generate scaffolding, boilerplate, documentation, bindings, everything from there. You can inherit from the existing Apexlang generators or create entirely new ones. It’s just TypeScript running on Deno and you can host your templates and generators anywhere.

## What’s next?

I didn’t start Apexlang but I recognized the value immediately when using it as WIDL. The original author (Phil Kedy) and I have joined up as part of [Candle](https://candle.dev/) to build tools that make everything about software easier. Once you start it’s hard to stop. We use Apexlang heavily on [NanoBus](http://github.com/nanobus/nanobus), our WebAssembly-oriented developer platform.

We’re in the process of fully converting Apexlang and its dependencies from a go/node hybrid to deno and WebAssembly. The apex CLI, parsers, and code generators are now fully deno but there may be some rough edges as we iron out the wrinkles.

We hang out in [our discord server](https://jsoverson.medium.com/discord.gg/candle) and are always happy to chat about Apexlang, WebAssembly, go, rust, or anything else. Come say hi!