Apexlang: Project Templates & Code Generation
Jarrod Overson
Posted on January 6, 2023
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
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'
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
}
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.
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;
}
}
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 "";
}
}
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...]
}
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'
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
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!
Posted on January 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.