Using Decoders in your API: The boundary between two worlds

Recently, I was working on a project with Josh and he taught me something he uses in Elm: Decoders for JSON.

Decoders transform your API types into your application's types. By doing this, you can make sure your application receives the correct data types and the keys are correct for each HTTP request.

Let's say you're working with an API and it returns the following JSON:

{
  "person": {
    "name": "Josh Adams",
    "age": 30,
    "github": "https://github.com/knewter"
  }
}

You receive this JSON. What if some key changes or if some type of the API changes? Your application will probably crash. Using Decoders makes sure that what we receive is completely correct. For Javascript, we are using Flow to make types in our application.

In our application we have PersonType as an Immutable Record:

import { Record } from 'immutable'
.
export default class PersonType extends Record({
  name: '',
  age: 0,
  github: ''
}) {
  name: string
  age: number
  github: string
}

Using immutability in our types help us avoid a series of common problems that could occur. This way your code will be substantially easier to work with.

Let's create our decoder. It gets our API type, and it transforms into our application type. In our case, PersonType.

Our code will look like:

  type ApiPerson = {
    name: string,
    age: number,
    github: string
  }

  decodePersonType(person: ApiPerson) : PersonType{
    return new PersonType({
      name: person.name,
      age: person.age,
      github: person.github
    })
  }

This will be our decoder. It gets our API type and transforms it into our application type. This file is the boundary between the two worlds: our API world and our Application world. Here we can make sure the types are correct.

We can use Decoders when we do our API calls, because the return of our API call will be decoded.

const Person = {
  get: (name: string): Promise<PersonType> => {
    return api.get(`${name}`).then(resp => decodePersonType(resp.data));
  }
};

Why should I use it?

There are so many reasons, but I'm going just to mention some of them:

  • All our code now uses our own types.
  • It turns server JSON into typed immutable records.
  • You make the conversion between your server data and application data very explicitly.
  • It adds one more validation phase in getting your data from your API.
  • Your app will now recognize weird JSON structures.
  • JSON decoders help you build complicated logic into smaller building blocks.

If the data isn't what you expect you can throw errors at this boundary by using validations in the decoders.

I found it really interesting, and I wanted to share it with you! I've been using similar things in other languages, but seeing the Elm community doing this just made me more excited to study more Elm.

If you want to know more about Elm, check out these videos:

Interesting Links about Elm Decoders: