Don't waste time building another API, let the machines make them for you with the Booster Framework!
Tags: api, graphql, productivity, opensource
Published: 2021-05-11 17:37:35

---
title: Don't waste time building another API, let the machines make them for you with the Booster Framework!
published: true
description: We live in the API era, if your application doesn't have an API, it's not a real application; but have you thought about what it takes to build a proper API? What if there were better ways to do it?
tags: API, GraphQL, productivity, opensource
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fuyzpbdceqop63bsrcav.jpg
---

Building a great API is not trivial, you have to design the data structure, choose the right connection mechanism, implement handlers for every endpoint, serialize/deserialize the data and validate it before accepting it in your business logic.

In addition to that, it is often necessary to build API clients for front-end applications or for other services in order to make use of the functionality offered by your service.

If you use [GraphQL](https://graphql.org/), the second part can definitely be simplified, as you can use any [standard GraphQL client](https://github.com/apollographql/apollo-client) to query the system, but [manually building and maintaining schemas and resolvers is still such a hassle that some teams might prefer to stay in REST](https://blog.logrocket.com/why-you-shouldnt-use-graphql/).

# Boost your API with CQRS!

The Booster Framework introduces a very different approach to building APIs: **not doing it at all!**

This is only possible thanks to the opinionated implementation of [the CQRS pattern](https://www.martinfowler.com/bliki/CQRS.html) and Booster's inference capabilities. Let's walk through [the structure of a typical Booster application](https://docs.booster.cloud/#/chapters/03_booster-architecture) before going back to APIs.

In a Booster application, most functionality is defined in commands. A [`Command`](https://docs.booster.cloud/#/chapters/03_booster-architecture?id=_1-command-and-command-handlers) is a data structure that has a handler, like this one that processes a backflip:

```typescript
@Command({ authorize: 'all' })
export class PerformABackflip {
  public constructor(
    readonly personName: string,
    readonly personAge: number
  ) {}

  public static async handle(command: PerformABackflip, register: Register): Promise<void> {
    const luck = Math.rand()
    if (command.personAge > 35 && luck > 0.5) {
      register.events(PersonSurvivedABackflip(command.personName)
    } else {
      callAnAmbulance()
      register.events(AmbulanceCalled(command.personName)
    }
  }
}
```

When a command handler is processed, it finishes by writing one or more events into the event store. In this case, a `PersonSurvivedABackflip` event for lucky or young people, or an `AmbulanceCalled` for the rest of us 😅.

An [`Event`](https://docs.booster.cloud/#/chapters/03_booster-architecture?id=_2-events) is just a data structure that looks like this:

```typescript
@Event
export class PersonSurvivedBackflip {
  public constructor(
    readonly personName: string
  ) {}

  /* We define the `entityID` method to help Booster match 
  the event with the corresponding entity */
  public entityID() {
    return this.personName
  }
}

```

These events are later [reduced into entities](https://docs.booster.cloud/#/chapters/03_booster-architecture?id=_4-entities-and-reducers) that represent the current state. The reduction is carried out by reducer functions that look like these:

```typescript
@Entity
export class Person {
  public constructor(
    readonly name: string,
    readonly backflipTrials: number,
    readonly backflipSuccesses: number,
  ) {}

  @Reduces(PersonSurvivedBackflip)
  public static reduceSurvival(
    event: PersonSurvivedBackflip, 
    currentPerson?: Person
  ): Person {
    return buildNextPersonObject(currentPerson, true)
  }

  @Reduces(AmbulanceCalled)
  public static reduceFailure(
    event: AmbulanceCalled,
    currentPerson?: Person
  ) {
    return buildNextPersonObject(currentPerson, false)
  }

  private static buildNextPersonObject(
    currentPerson?: Person, 
    success: boolean
  ) {
    const trials = (currentPerson?.backflipTrials ?? 0) + 1
    let successes = (currentPerson?.backflipSuccesses ?? 0)
    if (success) { successes++ }
    return new Person(
      event.personName,
      trials,
      successes
    )
  }
}
```

# Securing queries with Read Models

At this point, the CQRS design helped us to separate the data schema that the system accepts (commands) from the state data schema (entities). This in itself already simplifies the API design: The API schema could just match the command and entity schemas, and we could call that a nice API. But before calling it a day, we need to make an extra consideration; Allowing direct API access to entities would mean no restrictions on data access, so private fields like password hashes or bank accounts would become accessible. That's why Booster adds Read Models to the mix.

[`ReadModels`](https://docs.booster.cloud/#/chapters/03_booster-architecture?id=_5-read-models-and-projections) are eventual consistent caches of the internal state. They're not only a way to filter which fields you want to make accessible, but they can also aggregate related data or make small data transformations to optimize reads. Accessing read models is highly performant and they're updated in real-time when data changes, pushing these changes to the client applications. 

A typical read model that projects a single entity would look like this:

```typescript
@ReadModel({ authorize: 'all' })
export class PersonReadModel {
  public constructor(
    readonly name: string,
    readonly backflipTrials: number,
    readonly backflipSuccesses: number,
  ) {}

  @Projects(Person, 'name')
  public static projectPerson(
    entity: Person,
    currentState?: PersonReadModel
  ): ProjectionResult<PersonReadModel> {
    return new PersonReadModel(
      entity.name,
		 entity.backflipTrials,
      entity.backflipSuccesses
    )
  }
}
```

# Inferring APIs from code!

At this point, we could build a very nice, useful, and secure API by just copying the schemas from the classes decorated as `Command` or `ReadModel`, and [that's exactly what Booster does for you](https://docs.booster.cloud/#/chapters/04_features?id=graphql-api)!

Booster analyzes the class structure of all classes decorated as `@Command` or `@ReadModel` in compile-time, generating metadata that is used in deploy time to [generate a GraphQL schema and provision all the cloud resources required to make the application work](https://docs.booster.cloud/#/chapters/02_getting-started?id=_6-deployment), including API gateways, lambda functions, containers, permissions, and even the database tables to store the events.

For some people, this might look similar to maintaining the schema in a regular GraphQL schema file, because, at the end of the day, you're still expressing the schemas as `Command` and `ReadModel` classes, but Booster brings many extra advantages to the table:

1. Commands, Read Models, and all their usages are **type-checked in compile-time**, reducing or eliminating the likelihood of making mistakes.
2. No errors can be introduced when serializing/deserializing data because this is **done transparently** by the framework.
3. When you update a command or a read model, **the API is updated automatically**, you don't need to change any other files.
4. Resolvers are hidden under **higher-level abstractions** like commands or read models, so you don't need to deal with low-level protocol nuances.
5. All Read Models **support WebSockets by default**, so there is no need to implement any extras for real-time support in your applications.

To summarize, by writing highly semantic code and letting the machine do the heavy lifting, [Booster](https://booster.cloud) allows you to build fully functioning real-time APIs in a breeze, making everything else work out of the box, and saving a ton of time that you can use to add new use cases, write better tests, or manage elusive corner cases.

# Try it!

Booster is a 100% open-source project developed by [The Agile Monkeys](https://www.theagilemonkeys.com). You can [get its full potential for free and with no hidden fees](https://docs.booster.cloud/#/chapters/02_getting-started?id=installing-booster). The typical Booster application can be run on the free tier in [AWS](https://aws.amazon.com/), but it also has experimental support for [Azure](https://azure.microsoft.com/en-us/) and [Kubernetes](https://kubernetes.io/), so you can run it locally using [Minikube](https://minikube.sigs.k8s.io/docs/).

Learn more about Booster on [the official website](https://booster.cloud), [the documentation](https://docs.booster.cloud/#/), or [the Github project](https://github.com/boostercloud/booster).

If you try it out, be sure to let us know what you thought of it on [the project’s Discord channel](https://discord.gg/bDY8MKx)!