DEV Community

shindex
shindex

Posted on

Type-safe HTTP requests with Nuxt

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

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.

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

$ cd aspida

Now you are ready to use typescript in nuxt!


Building an ASPIDA environment

First, you need to install aspida.

$ npm install @aspida/axios

Edit the package.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.

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!, which returns country code and a greeting when you give a language code.

export default {
  ...

  plugins: [
    '~/plugins/api'
  ],

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

  ...
}

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

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.

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

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

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

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

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

Now, let's start nuxt.

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

<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

Aspida warns you of your mistakes.

Alt Text

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

Alt Text

Top comments (3)

Collapse
 
luckyluggi profile image
Lukas Eiter

Hy, thanks for your post.
When i try to follow it i get the Error:
"Property '$axios' does not exist on type 'Context'."
it occurs on the line "const plugin: Plugin = ({ $axios }, inject) => inject('api', api(axiosClient($axios)))" in api.ts

do i need to import $axios here?

Collapse
 
solufa profile image
Solufa

Hi, I'm aspida developer.
I am very happy with your message.
The Error will be solved this way.

tsconfig.json

{
  "types": [
      "@types/node",
      "@nuxt/types",
      "@nuxtjs/axios" // <- add!
  ]
}

You don't need to import $axios.

One more piece of advice on a side note.
If you feel the API response is slow, you can switch to SPA mode.

nuxt.config.js

export default {
  mode: 'spa',
  ...
}
Collapse
 
luckyluggi profile image
Lukas Eiter

thanks, that solved the error.