Skip to content

mix plain JavaScript object and TypeScript definition to build schema

License

Notifications You must be signed in to change notification settings

GuillaumeJasmin/typescript-request-schema

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TypeScript Request Schema


npm package


It's time to type your HTTP requests


npm i typescript-request-schema


Motivation

API request have always the same data type: url, method, query params, body, and response. This package aim to easily define their types.

Quick example

import { Config, Response } from 'typescript-request-schema'

const schema = {
  updateArticle: {
    url: (pathParams: { id: string; }) => `/articles/${pathParams.id}`,
    method: 'PATCH',
    queryParams: {} as {
      accessToken: string;
    },
    data: {} as {
      title?: string;
      content?: string;
    },
    response: {} as {
      id: string;
      title: string;
      content: string;
      updatedAt: string;
    }
  }
}

type Schema = typeof schema
type RequestName = keyof Schema
type ExtraConfig = { ... }
type RequestConfig<T extends RequestName> = Config<T, Schema, ExtraConfig>
type RequestResponse<T extends RequestName> = Promise<Response<T, Schema>>

function request<T extends RequestName>(config: RequestConfig<T>): RequestResponse<T> {
  const { name, data, queryParams, pathParams, ...restConfig } = config
  const { url, method } = schema[name]

  ...
}

const article = await request({
  name: 'updateArticle',
  pathParams: {
    id: '...'
  },
  queryParams: {
    accessToken: '...'
  },
  data: {
    title: '...'
  }
})

It's up to you to create your own request implementation.

queryParams, data and response are only use as TypeScript type thanks to as keyword.

Full example with window.fetch

import { Config, Response, validateSchema } from 'typescript-request-schema'
import { schema } from './schema'

export const schema = {
  updateArticle: {
    url: (pathParams: { id: string; }) => `/articles/${pathParams.id}`,
    method: 'PATCH',
    queryParams: {} as {
      accessToken: string;
    },
    data: {} as {
      title?: string;
      content?: string;
    },
    response: {} as {
      id: string;
      title: string;
      content: string;
      updatedAt: string;
    }
  }
}

validateSchema(schema) // only use as TS checker

type Schema = typeof schema
type RequestName = keyof Schema
type ExtraConfig = NonNullable<Parameters<typeof fetch>[1]>
type RequestConfig<T extends RequestName> = Config<T, Schema, ExtraConfig>
type RequestResponse<T extends RequestName> = Promise<Response<T, Schema>>

function request<T extends RequestName>(config: RequestConfig<T>): RequestResponse<T> {
  const { name, data, queryParams, pathParams, ...restConfig } = config
  const { url, method } = schema[name]

  const urlWithPathParams = (typeof url === 'function' && pathParams)
    ? url(pathParams)
    : url

  const queryParamsAsString = Object.entries(queryParams || {})
    .map(([key, value]) => `${key}=${value}`)
    .join('&')

  const baseUrl = 'http://api.website.com'
  const fullUrl = `${baseUrl}${urlWithPathParams}?${queryParamsAsString}`

  return fetch(fullUrl, {
    method,
    body: data ? JSON.stringify(data) : undefined,
    ...restConfig
  }).then(res => res.json())
}

const article = await request({
  name: 'updateArticle',
  pathParams: {
    id: '...'
  },
  queryParams: {
    accessToken: '...'
  },
  data: {
    title: 'new title'
  }
})

with axios ?

It's up to you to define your own request implementation, so you can use any request library.

import axios, { AxiosRequestConfig, AxiosPromise } from 'axios'

...

type Schema = typeof schema
type RequestName = keyof Schema
type ExtraConfig = AxiosRequestConfig
type RequestConfig<T extends RequestName> = Config<T, Schema, ExtraConfig>
type RequestResponse<T extends RequestName> = AxiosPromise<Response<T, Schema>>

function request<T extends RequestName>(config: RequestConfig<T>): RequestResponse<T> {
  const { name, data, queryParams, pathParams, ...restConfig } = config
  const { url, method } = schema[name]

  const urlWithPathParams = (typeof url === 'function' && pathParams)
    ? url(pathParams)
    : url

  const baseUrl = 'http://api.website.com'

  return axios.request({
    url: `${baseUrl}${urlWithPathParams}`,
    method,
    params: queryParams,
    data,
  })
}

API

  • Config<RequestName, Schema, ExtraConfig>
  • Response<RequestName, Schema>
  • validateSchema() - use for TS check

IntelliSense examples

  • name

name

  • pathParams

name

  • queryParams

name

  • data

name

  • response

name

  • extraProperties

name

Advanced config

You can change the key of each properties

interface Conf {
  RouteNameKey: 'name';
  PathParamsKey: 'pathParams';
  QueryParamsKey: 'queryParams';
  URLKey: 'url';
  DataKey: 'data';
  ResponseKey: 'response';
  MethodKey: 'method';
}

const schema = {
  ...
}

validateSchema<Conf>(schema)

...

type RequestConfig<T extends RequestName> = Config<T, Schema, ExtraConfig, Conf>

About

mix plain JavaScript object and TypeScript definition to build schema

Resources

License

Stars

Watchers

Forks

Packages

No packages published