Type-safe HTTP requests with Nuxt
Tags: nuxt, typescript, aspida
Published: 2020-05-04 08:29:25

## Problems with HTTP requests

Type inference for HTTP requests in the editor is not enough. Any change in the API reference requires a change in the source code, which is a very tedious and error-prone process.

Aspida is the OSS that solves it.
For a more detailed explanation of ASPIDA, please see the following article.

["Aspida" is a tool to give types to HTTP clients](https://dev.to/shindex/aspida-is-a-tool-to-give-types-to-http-clients-1mkh)

This article presents an example of a type-sefe HTTP request in the Nuxt environment using Aspida.

---

## Building typescript environment with Nuxt

It used to be a little difficult, but now you can easily build a typescript environment just by using the `create-nuxt-app`.

```bash
$ npx create-nuxt-app aspida

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project in aspida
? Project name aspida
? Project description My exceptional Nuxt.js project
? Author name shindex
? Choose programming language TypeScript
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose the runtime for TypeScript @nuxt/typescript-runtime
? Choose Nuxt.js modules Axios
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
```

In the `Choose programming language` field, select **typescript**.
Also, since we'll be using axios later, let's select **axios** in the `Choose Nuxt.js modules` section.

Let's go to the `aspida` directory that was created.

```bash
$ cd aspida
```

Now you are ready to use typescript in nuxt!

---

## Building an ASPIDA environment

First, you need to install aspida.

```bash
$ npm install @aspida/axios
```

Edit the `package.json`.

```json
{
  ...

  "scripts": {
    "dev": "aspida --watch & nuxt-ts",   // this line is edited.
    "build": "nuxt-ts build",
    "generate": "nuxt-ts generate",
    "start": "nuxt-ts start",
  },

  ...
}
```

Registering $api in nuxt plugins. Create `plugins/api.ts`.

```typescript
import { Plugin } from '@nuxt/types'
import axiosClient from '@aspida/axios'
import api, { ApiInstance } from '~/apis/$api'

declare module 'vue/types/vue' {
  interface Vue {
    $api: ApiInstance
  }
}

const plugin: Plugin = ({ $axios }, inject) => inject('api', api(axiosClient($axios)))

export default plugin
```

Edit the `nuxt.config.js`. This time, we will use an API called [Hello, salut!](https://www.fourtonfish.com/hellosalut/hello/), which returns country code and a greeting when you give a language code. 

```javascript
export default {
  ...
 
  plugins: [
    '~/plugins/api'
  ],

  axios: {
    baseURL: 'https://fourtonfish.com/'
  },

  ...
}
```

`Hello, salut!` doesn't work without a / just before the query.

- Correct  : https://fourtonfish.com/hellosalut/?lang=en
- Incorrect: https://fourtonfish.com/hellosalut?lang=en

By writing a configuration file, you can solve various problems like this. The Aspida configuration file is written in the project root, `aspida.config.js`.

```javascript
module.exports = { trailingSlash: true }
```

---

## Create a type definition file

Create the required types at `/apis/@types.ts`. This file should only contain the type. The following types are the response types for `Hello, salut!`.

```typescript
export type Greeting = {
  code: string
  hello: string
}
```

Write the type definition for the http request in `apis/hellosalut/index.ts`.

```typescript
import { Greeting } from '~/apis/@types'

export type Methods = {
  get: {
    query: {
      lang: string 
    }
    resBody: Greeting
  }
}
```

Now, let's start nuxt.

```bash
$ npm run dev
```

`apis/$api.ts` is generated at the same time as nuxt is started. 

It's going very well. Let's restart the editor here to reload the type inference.

---

## Using type-secure HTTP requests

Now, take a look at an HTTP request using aspida. The following is `pages/example.vue`.

```vue
<template>
  <table>
    <thead>
      <tr>
        <th>country code</th>
        <th>greeting</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="greeting in greetings" :key="greeting.code">
        <td>{{ greeting.code }}</td>
        <td>{{ greeting.hello }}</td>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts">
import Vue from 'vue'
import { Greeting } from '~/apis/@types'

export default Vue.extend({
  data() {
    return {
      greetings: [] as Greeting[]
    }
  },
  
  async fetch() {
    const langs = [ 'en', 'zh', 'de', 'fr', 'ja' ]

    this.greetings = (
      await Promise.all(
        langs.map((lang) => this.$api.hellosalut.$get({ query: { lang } }))
      )
    ).map(({ code, hello }) => ({
      code,
      hello: hello.startsWith('&#')
        ? String.fromCharCode(
            ...hello
              .slice(2, -1)
              .split(';&#')
              .map((n) => +n)
          )
        : hello
    }))
  }
})
</script>
```

**type inference does not work in `asyncData`**. Let's use `fetch` instead.
You can access the `$api` from `this`.

You can see that it is possible to write an HTTP request type-sefely as follows.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gmk40brqpkmc4mocpnfv.gif)

Aspida warns you of your mistakes.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/taz34kylkhasnmz1h3ay.png)

Run `npm run dev` and check the `localhost:3000/example`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cc5qlrvmaqk5jb4tz4wz.png)