npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

effector-http-api

v2.5.0

Published

Setup API with ease

Downloads

578

Readme

effector-http-api

Installation

Dependencies

npm i effector-http-api

or is you use yarn

yarn add effector-http-api

Also install peer-dependencies

yarn add axios effector

Usage

// src/shared/api/my-backend.ts`
import { createHttp } from 'effector-http-api'
import type { User, CreateUserDto, UpdateUserDto } from './models'

const instance = axios.create()
const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
 // createRoute<Dto, Contract>() returns Effect<Dto, Contract>
 getAll: http.createRoute<void, User[]>({
  url: '/',
  // "GET" method is set by default, no need to add this line
  method: 'GET'
 }),

 // dto provided to route pass to AxiosRequestConfig['data']
 create: http.createRoute<CreateUserDto, User>({
  url: '/',
  method: 'POST'
 }),

 // In 'GET' request `dto` will be passed to url as query
 getFiltered: http.createRoute<FiltersDto, User[]>({
  url: '/',
 }),

 // If you need to customize behavior of dto — use callback instead of config
 update: http.createRoute<{ id: User['id'], data: UpdateUserDto }, User>(({id, data}) => ({
  url: `/${id}`,
  method: 'PUT',
  data
 })),

 get: http.createRoute<User['id'], User>((id) => ({
  url: `/${id}`,
 })),


 createByUpload: http.createRoute<{ file: File }>({
  url: '/upload/users',
  method: 'POST',
  // dto provided with this flag converts to FormData under the hood
  formData: true
 })
});

const api = routesConfig.build();

export { api }

Headers

To attach headers to requests, call http.headers(unit), and pass as payload Unit<RawAxiosRequestHeaders>

http.headers(unit) it alias sample({clock: unit, target: $innerHttpHeaders }), if you need replace $innerHttpHeaders with your store (in case of defaultValues; or with effector-storage/local, which don't trigger $store.updates) You can pass custom store by createHttp(instance, $defaultHeaders)

Usage

import { createEvent, createStore } from "effector";
import { RawAxiosRequestHeaders } from "axios";


const headersChanged = createEvent<RawAxiosRequestHeaders>()
createHttp(instance).headers(headersChanged);

// or
const $headers = createStore<RawAxiosRequestHeaders>({})
createHttp(instance, $headers);

Options

To add custom option to route use http.createRoutesConfig.options(options)

batch: boolean

If route called multiple times while request is pending, calls will be batched with call which start a request

const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
 route: http.createRoute<void,User[]>({ url: '/' })
});

routesConfig.options({
 route: {
  batch: true
 }
});

const api = routesConfig.build();

Validation:

Validate response from backend before resolve request

yup validator example

import { number, object, string } from "yup";

const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
 getUsers: http.createRoute<void, User[]>({url: '/'}),
});

routesConfig.validation({
 getUsers: object({
  id: string().required(),
  age: number().required()
 }).required()
})


const api = routesConfig.build();

Custom validator example

import { ValidationSchema } from "effector-http-api";

const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
 getUsers: http.createRoute<void, User[]>({url: '/'}),
});

class MyCustomValidator implements ValidationSchema<User> {
 public validate = (user: User) => {
  //... validate user
  // return Promise.resolve() if passed
  // return Promise.reject() if not
 }
}

routesConfig.validation({
 getUsers: new MyCustomValidator()
})


const api = routesConfig.build();

Mock:

Configuration for return mock response instead calling request

const mockedUsers: User[] = [
 {id: 1, name: 'Mocked User 1'},
 {id: 2, name: 'Mocked User 2'}
]

const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
 getUsers: http.createRoute<void, User[]>({ url: '/' }),
 getUser: http.createRoute<User['id'], User>({ url: '/' }),
});

routesConfig.mocks({
 getUsers: {
   /**
   * Same as `options.batch`.
   * Usefull, then mock responseFn has timeouts or `mock.delay`
   */
  batch: true,
  
  delay: 1500,
  response: mockedUsers
 },
 getUser: {
  delay: 1000,
  response: (id) => mockedUsers.find(user => user.id === id)
 }
});

const api = routesConfig.build();

RawResponseFx

By default all routes map responses

(response: AxiosResponse) => response.data

Raw version of route, without mapper, accessible by prop on route

const http = createHttp(instance);

const routesConfig = http.createRoutesConfig({
  getData: http.createRoute<void, User[]>({ url:'/' }) 
});

const api = routesConfig.build();

api.getData // with mappings
api.getData.rawResponseFx // without mappings

BaseHttpFx

All routes attached to single baseRequestFx

Accessible from http.baseRequestFx

const http = createHttp(instance);

const requestWith401statusFailed = sample({
  clock: http.baseHttpFx.failData,
  filter: is401Error,
});

export { requestWith401statusFailed }

Instance

New http instance can be set by http.setHttpInstance(newHttpInstance)

//api/config.ts
const http = createHttp(instance);

export {http};

//setup-tests.ts
import { http } from 'api';
import { createMockHttpInstance } from 'tests/mocks'

http.setHttpInstance(createMockHttpInstance())

ForkAPI

See more examples in tests

//api/config.ts
const $instance = createStore(
    axios.create({ baseURL: 'https://api.com' }), 
    { serialize: 'ignore' }
);

const http = createHttp($instance);

export { http };

//pages/home.ts
const getServerSideProps = async () => {
    const newInstance = axios.create({ 
        baseURL: 'https://api.com' 
    });
    
    //interceptors only be created for this instance
    newInstance.interseptors.request.use(...);

    // replace store in fork creation phase
    const scope1 = fork({ values: [[http.$instance, newInstance]] });
    

    // or replace store by scoped event
    const scope2 = fork({});
    await allSettled(
        http.updateHttpInstance, 
        { scope: scope2, params: newInstance }
    );
    
    return {...}
}

Generate api layer from swagger

  1. Install codegen module
npm i swagger-typescript-api -D

or is you use yarn

yarn add swagger-typescript-api -D
  1. Create a script file
// {ROOT}/scripts/codegen.js

const {generateApi} = require('swagger-typescript-api');
const path = require('path');

const fileName = 'api.gen.ts';
const outputDir = path.resolve(process.cwd(), './src/shared/api');
const urlToSwaggerSchema = 'https://backend.com/swagger.json';

const pathToTemplate = path.resolve(process.cwd(), 'node_modules', 'effector-http-api/codegen-template');

generateApi({
 name: fileName,

 output: outputDir,

 url: urlToSwaggerSchema,

 httpClientType: 'axios',

 generateClient: true,

 templates: pathToTemplate,
});
  1. Create a config file
// {ROOT}/src/shared/api/config.ts

import axios from "axios";
import { createHttp } from 'effector-http-api'

const instance = axios.create();

const http = createHttp(instance);

export { http }
  1. Run this command to generate api layer
node ./scripts/codegen.js
  1. Check generated file at src/shared/api/api.gen.ts

  2. Add headers, options, mock, validation. Build routes and export api ready for usage


Babel-preset

//babel.config.js

module.exports = {
  presets: ["effector-http-api/babel-preset"]
}