1.5.3 • Published 11 months ago

@nfq/typed-next-api v1.5.3

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

@nfq/typed-next-api

EsLint


  1. Description
  2. Getting started
    1. Installation
    2. PeerDependencies
  3. Usage
    1. Api Routes File Structure
      1. An Get Request
      2. An Post Request
      3. Without anything
      4. Returning an error
    2. Frontend Usage
      1. useApi
      2. useMutateApi
      3. api
      4. preloadData
  4. Props
    1. TypedRoute
    2. connectable
    3. api
    4. useMutateApi
    5. preloadData
  5. Provided enums
    1. HTTP_METHODS
    2. HTTP_STATUS
  6. Support

Description: License: MIT

This is an package to create typed api routes for next.js. It helps with getting the contract between your nextjs api and the frontend in sync. It also helps with the typing of the api routes, the return types, the request types and body and query types.


Getting started

To setup the project locally follow the next steps:

Installation

To install the package run

npm install @nfq/typed-next-api

if you are on yarn

yarn add @nfq/typed-next-api

or on pnpm

pnpm install @nfq/typed-next-api

PeerDependencies:

The following PeerDependencies are needed so the module does work:

  • next >= 12.0.0
  • typescript >= 4.9.5

Usage

Using the typed routes is simple. First you have to create your api routes. You can do this by creating a file in the api folder of your nextjs project. The file should have the following structure:

Api Routes File Structure:

An Get Request:

import {connectable, HTTP_METHODS, HTTP_STATUS, TypedRoute} from '@nfq/typed-next-api';
import nextConnect from 'Server/nextConnect';

// This is the type of your query params.
interface QueryType {
  page: string;
}

export const getRequestName = TypedRoute(HTTP_METHODS.GET, async (req: NextApiRequest, res: NextApiResponse, body: unknown, query: QueryType) => {
    // you can directly access the query params here.
    const {page} = query;

    // youre route code here.

    /**
     * Your return Value is what gets typed in the frontend. It should be an
     * object with a status and a data property.
     */
    return {
        data: {
        // your response body here.
        },
        status: HTTP_STATUS.OK
    };
};

/**
 * To get no typing errors you have to wrap your route with the connectable
 * function.
 */
export default nextConnect().get(connectable(getRequestName));

An Post Request:

import {connectable, HTTP_METHODS, TypedRoute} from '@nfq/typed-next-api';
import nextConnect from 'Server/nextConnect';

// This is the type of your body params.
interface BodyType {
  page: string;
}

export const postRequestName = TypedRoute(HTTP_METHODS.POST, async (req: NextApiRequest, res: NextApiResponse, body: BodyType) => {
    // you can directly access the body params here.
    const {page} = body;

    // youre route code here.

    /**
     * Your return Value is what gets typed in the frontend. It should be an
     * object with a status and a data property.
     */
    return {
        data: {
        // your response body here.
        },
        status: HTTP_STATUS.CREATED
    };
};

/**
 * To get no typing errors you have to wrap your route with the connectable
 * function.
 */
export default nextConnect().post(connectable(postRequestName));

Without anything:

import {connectable, HTTP_METHODS, TypedRoute} from '@nfq/typed-next-api';
import nextConnect from 'Server/nextConnect';

export const getRequestName = TypedRoute(HTTP_METHODS.POST, async () => {
    // youre route code here.

    /**
     * Your return Value is what gets typed in the frontend. It should be an
     * object with a status and a data property.
     */
    return {
        data: {
        // your response body here.
        },
        status: HTTP_STATUS.OK
    };
};

/**
 * To get no typing errors you have to wrap your route with the connectable
 * function.
 */
export default nextConnect().get(connectable(getRequestName));

Returning an error:

import {connectable, HTTP_METHODS, TypedRoute} from '@nfq/typed-next-api';
import nextConnect from 'Server/nextConnect';

export const getRequestName = TypedRoute(HTTP_METHODS.POST, async () => {
    // youre route code here.

    /**
     * Your return Value is what gets typed in the frontend. It should be an
     * object with a status and a message property if you want to transport
     * an error in the frontend.
     */
    return {
        message: 'This is an error message',
        status: HTTP_STATUS.NOT_FOUND
    };
};

/**
 * To get no typing errors you have to wrap your route with the connectable
 * function.
 */
export default nextConnect().get(connectable(getRequestName));

This will create a typed route for the frontend. That can be used to get usage hints in your application.

To use your typed route in your frontend you have 4 functions for that.

Frontend Usage:

useApi:

This hook fetches your data and returns the data, error and isValidating state. The mutate function is the same as the mutate function from useSWR.

import {useApi} from '@nfq/typed-next-api';
import {getRequestName} from 'pages/api/getRequestName';

const DataLoadingComponent = () => {
    const {data, error, isValidating, mutate} = useApi<typeof getRequestName>('the api route path');
    
    // Is true if your data is loading right now.
    if (isValidating) {
        return <div>Loading...</div>;
    }
    
    // An error object holding two properties: info and status.
    if (error) {
        return <div>Error: {error.info}</div>;
    }
    
    // The response data from your api routes data property.
    return <div>{data}</div>;
};

useMutateApi:

This hook prepares an request for later usage. You can use the trigger function to trigger the request. The reset function resets the state of the hook. The mutate function is the same as the mutate function from useSWR.

import {HTTP_METHODS, useMutateApi} from '@nfq/typed-next-api';
import {postRequestName} from 'pages/api/postRequestName';

const DataLoadingComponent = () => {
    const {data, error, isMutating, reset, trigger} = useMutateApi<typeof postRequestName>('the api route path');
    
    const triggerRequest = () => {
        trigger({
            /**
             * The body params of your request. This is only available if an 
             * body type is defined in your route.
             */
            asFormData: false,
            /**
             * The body params of your request. This is only available if an 
             * body type is defined in your route.
             */
            body: {page: '1'},
            // The headers of your request.
            headers: {'Content-Type': 'application/json'},
            /**
             * The method to use. This is forced to be the same as the method
             * defined in your route.
             */
            method: HTTP_METHODS.POST,
            /**
             * The query params of your request. This is only available if an
             * query type is defined in your route.
             */
            query: {page: '1'}
        });
    };

    // Is true if your data is sending right now.
    if (isMutating) {
        return <div>Loading...</div>;
    }
    
    // An error object holding two properties: info and status.
    if (error) {
        return <div>Error: {error.info}</div>;
    }
    
    // The response data from your api routes data property.
    return <div>{data}</div>;
};

api:

This function is the same as useApi with the difference that it can be used outside of react. It returns a promise with the data or an error object.

import {api} from '@nfq/typed-next-api';
import {postRequestName} from 'pages/api/postRequestName';

const normalFunction = async () => {
    const data = await api<typeof postRequestName>(
        'the api route path',
        {
            body: {page: '1'},
            headers: {'Content-Type': 'application/json'},
            method: HTTP_METHODS.POST
        }
    );

    // The response data from your api routes data property.
    return data;
};

preloadData:

This function does not use your type information but is usefull to preload data needed later on.

import {preloadData, useApi} from '@nfq/typed-next-api';
import {getRequestName} from 'pages/api/getRequestName';

void preloadData('the api route path');

const DataLoadingComponent = () => {
    const {data, error, isValidating, mutate} = useApi<typeof getRequestName>('the api route path');
    
    // Never true because data is already loaded.
    if (isValidating) {
        return <div>Loading...</div>;
    }
    
    // An error object holding two properties: info and status.
    if (error) {
        return <div>Error: {error.info}</div>;
    }
    
    // The response data is already loaded.
    return <div>{data}</div>;
};

Props

TypedRoute Props

ParamtyperequiredDescription
methodHTTP_METHODSxOne of the provided HTTP_METHODS enum types.
routeHandlerFunctionxAn route handler function getting an req, res, body and query object. (Typings get defined through the last two objects.)

connectable Props

ParamtyperequiredDescription
typedRouteFunctionTypedRoutexAnd route handler defined through the TypedRoute function. (Effectively the return of the TypedRoute function)

api Props

ParamtyperequiredDescription
urlStringxThe api url.
optionsObjectAn options object with the following options:
options.bodyObjectThe body data for this route. Only applicable if an body is required in the handler function.
options.headersObjectAn headers object to customize the headers send to the server.
options.methodHTTP_METHODSOne of the provided HTTP_METHODS enum types. Has to match the provided type defined in the route.

useApi Props

ParamtyperequiredDescription
urlStringxThe api url.
swrOptionsObjectThe useSwr options object more information on that can get read at: SWR Docs
typeParamtypeThe type of the route handler function. This is used to define the return type of the hook.

useMutateApi Props

ParamtyperequiredDescription
urlStringxThe api url.
swrOptionsObjectThe useSwrMutation options object more information on that can get read at: SWR Docs
typeParamtypeThe type of the route handler function. This is used to define the return type of the hook.

preloadData Props

ParamtyperequiredDescription
urlStringxThe api url.

Provided enums

HTTP_METHODS

KeyValue
DELETEDELETE
GETGET
OPTIONSOPTIONS
PATCHPATCH
POSTPOST
PUTPUT

HTTP_STATUS

KeyValue
CONTINUE100
SWITCHING_PROTOCOLS101
PROCESSING102
EARLY_HINTS103
OK200
CREATED201
ACCEPTED202
NON_AUTHORITATIVE203
NO_CONTENT204
RESET_CONTENT205
PARTIAL_CONTENT206
MULTI_STATUS207
ALREADY_REPORTED208
IM_USED226
MULTIPLE_CHOICES300
MOVED_PERMANENTLY301
FOUND302
SEE_OTHER303
NOT_MODIFIED304
TEMPORARY_REDIRECT307
PERMANENT_REDIRECT308
BAD_REQUEST400
UNAUTHORIZED401
PAYMENT_REQUIRED402
FORBIDDEN403
NOT_FOUND404
METHOD_NOT_ALLOWED405
NOT_ACCEPTABLE406
PROXY_AUTH_REQUIRED407
REQUEST_TIMEOUT408
CONFLICT409
GONE410
LENGTH_REQUIRED411
PRECONDIRTION_FAILED412
PAYLOAD_TOO_LARGE413
URI_TOO_LONG414
UNSUPPORTED_MEDIA_TYPE415
RANGE_NOT_SATISFIABLE416
EXPECTATION_FAILED417
IM_A_TEAPOT418
MISDIRECTED_REQUEST421
UNPROCESSABLE_CONTENT422
LOCKED423
FAILED_DEPENDENCY424
TOO_EARLY425
UPGRADE_REQUIRED426
PRECONDITION_REQUIRED428
TOO_MANY_REQUESTS429
REQUEST_HEADER_FIELDS_TOO_LARGE431
UNAVAILABLE_FOR_LEGAL_REASONS451
INTERNAL_SERVER_ERROR500
NOT_IMPLEMENTED501
BAD_GATEWAY502
SERVICE_UNAVAILABLE503
GATEWAY_TIMEOUT504
HTTP_VERSION_NOT_SUPPORTED505
VARIANT_ALSO_NEGOTIATES506
INSUFFICIENT_STORAGE507
LOOP_DETECTED508
NOT_EXTENDED510
NETWORK_AUTHENTICATION_REQUIRED511

Support

Christoph Kruppe - https://github.com/ckruppe - c.kruppe@nfq.de

1.5.3

11 months ago

1.5.2

11 months ago

1.5.1-beta

1 year ago

1.5.0

1 year ago

1.5.0-beta

1 year ago

1.4.10

1 year ago

1.4.9

1 year ago

1.4.8

2 years ago

1.4.7

2 years ago

1.4.6

2 years ago

1.4.5

2 years ago

1.4.4

2 years ago

1.4.3

2 years ago

1.4.2

2 years ago

1.4.1

2 years ago

1.4.0-test

2 years ago

1.4.0

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.3.0

2 years ago

1.2.0

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago