Skip to content

A typed fetch client for openapi-typescript for use with SvelteKit

License

Notifications You must be signed in to change notification settings

cocreators-ee/apity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

52 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“˜οΈ apity - Typed API client for Svelte and SvelteKit

A typed fetch client for openapi-typescript compatible with SvelteKit's custom fetch

Installation

npm install @cocreators-ee/apity

Or

pnpm add @cocreators-ee/apity

Features

  • Support of JSON request and responses from OpenAPI 3.0
  • Support of {#await} syntax in Svelte templates
  • Compatibility with SvelteKit's fetch in load functions
  • Request reloading
  • Configuration of default fetch options

On the roadmap:

  • Caching of subsequent requests with the same URL and parameters

Live demo

Apity has a live demo with code samples. Also you can read an introductionary blog post.

Usage

Generate typescript definition from schema

Before working with the library, you need to generate an API spec using openapi-typescript:

npx openapi-typescript https://petstore3.swagger.io/api/v3/openapi.json --output src/petstore.ts

πŸš€ https://petstore3.swagger.io/api/v3/openapi.json β†’ file:./src/petstore.ts [870ms]

Using Apity

Configure Apity instance and generate functions for making API calls:

// File: api.ts

import { Apity } from '@cocreators-ee/apity'
import type { paths } from 'src/petstore'

const apity = Apity.for<paths>()

// global configuration
apity.configure({
  // Base URL to your API
  baseUrl: 'https://petstore.swagger.io/v2',
  // RequestInit options, e.g. default headers
  init: {
    // mode: 'cors'
    // headers: {}
  },
})

// create fetch operations
export const findPetsByStatus = apity
  .path('/pet/findByStatus')
  .method('get')
  .create()
export const addPet = apity.path('/pet').method('post').create()

Each API call is represented as a request object that has the following properties:

type ApiRequest<R = any> = {
  // Svelte store containing the response of the API call.
  readonly resp: Writable<ApiResponse<R> | undefined>

  // Svelte store that contains a promise for an API call.
  // If you reload the request using reload() function, this store will be updated.
  readonly ready: Writable<undefined | Promise<ApiResponse<R>>>

  // Function that reloads the request with the same parameters.
  reload: () => Promise<ApiResponse<R>>

  // Promise for the API call.
  // Useful for server code and places where you can't use the `ready` store.
  result: Promise<ApiResponse<R>>
}

Each response is a Svelte store returning either an undefined, or the following object:

type SuccessfulResp<R> = {
  ok: true
  // Typed object for a successful request. Built from the OpenAPI spec
  data: R
  // HTTP status code
  status: number
}

type FailedResp = {
  ok: false
  data: any
  // HTTP status code
  status: number
}

type ApiResponse<R> = SuccessfulResp<R> | FailedResp

Error handling

There are certain conditions under which an API request could throw an exception without actually reaching the desired server, for example, unpredictable network issues. For such cases, the api response will contain a status set to a negative number, indicating that an exception was thrown.

{
  ok: false,
  status: -1,
  data: undefined,
}

Using Apity with await syntax in templates

Assuming you've created an src/api.ts from using Apity section:

<script lang="ts">
  import { findPetByStatus } from 'src/api.ts'
  const request = findPetByStatus({ status: 'sold' })
  const petsReady = request.ready
</script>

<div>
  {#await $petsReady}
    <p>Loading..</p>
  {:then resp}
    {#if resp.ok}
      {#each resp.data as pet}
        <p>{pet.name}</p>
      {/each}
    {:else}
      <p>Error while loading pets</p>
    {/if}
  {/await}

  <button on:click={() => {request.reload()}}>
    Reload pets
  </button>
</div>

Subscribing to response store

Assuming you've created an src/api.ts from using Apity section:

<script lang="ts">
  import { findPetByStatus } from 'src/api.ts'
  const request = findPetByStatus({ status: 'sold' })
  let names = []

  request.resp.subscribe(resp => {
    if (resp.ok) {
      names = resp.data.map(pet => pet.name)
    }
  })
</script>

<div>
  {#each names as name}
    <p>{name}</p>
  {/each}
</div>

Using in load functions

Fetch operations support SvelteKit's load function from +page.ts and +page.server.ts.

Assuming you've created an src/api.ts from using Apity section:

import { findPetByStatus } from 'src/api.ts'

export async function load({ fetch }) {
  const request = findPetByStatus({ status: 'sold' })
  const resp = await request.result
  if (resp.ok) {
    return { pets: resp.data, error: undefined }
  } else {
    return { pets: [], error: 'Failed to load pets' }
  }
}

Financial support

This project has been made possible thanks to Cocreators. You can help us continue our open source work by supporting us on Buy me a coffee.

"Buy Me A Coffee"