DEV Community

Cover image for Apexlang: Project Templates & Code Generation
Jarrod Overson
Jarrod Overson

Posted on

Apexlang: Project Templates & Code Generation

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 & apexlang.io homepage

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 or scripts to make this process buttery smooth.

Tools like yeoman, degit, and cargo 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 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

$ 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
Enter fullscreen mode Exit fullscreen mode

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. 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.

spec: apex.axdl
plugins:
  - 'https://raw.githubusercontent.com/apexlang/codegen/main/src/typescript/plugin.ts'
Enter fullscreen mode Exit fullscreen mode

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:

namespace "greeting.v1"

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

type Person {
  firstName: string
  lastName: string
}
Enter fullscreen mode Exit fullscreen mode

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:

$ 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.
Enter fullscreen mode Exit fullscreen mode

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

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

class GreeterImpl implements Greeter {
  sayHello(to: Person): string {
    return "";
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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...]
}
Enter fullscreen mode Exit fullscreen mode

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.)

generates:
  API.md:
    module: https://deno.land/x/apex_codegen/markdown/mod.ts
    config:
      title: 'My Awesome Project'
Enter fullscreen mode Exit fullscreen mode

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 project.

This is the markdown we generate:

# 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
Enter fullscreen mode Exit fullscreen mode

There are generators for C#, Go, TinyGo, Rust, Java, Protobuf, OpenAPI, JSONSchema, Python, and more. 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 to build tools that make everything about software easier. Once you start it’s hard to stop. We use Apexlang heavily on 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 and are always happy to chat about Apexlang, WebAssembly, go, rust, or anything else. Come say hi!

Top comments (0)