@nfq/typed-next-api
TypeScript icon, indicating that this package has built-in type declarations

1.5.1-beta • Public • Published

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

(back to top)


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

(back to top)


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>;
};

(back to top)


Props

TypedRoute Props

Param type required Description
method HTTP_METHODS [x] One of the provided HTTP_METHODS enum types.
routeHandler Function [x] An route handler function getting an req, res, body and query object. (Typings get defined through the last two objects.)

connectable Props

Param type required Description
typedRouteFunction TypedRoute [x] And route handler defined through the TypedRoute function. (Effectively the return of the TypedRoute function)

api Props

Param type required Description
url String [x] The api url.
options Object An options object with the following options:
options.body Object The body data for this route. Only applicable if an body is required in the handler function.
options.headers Object An headers object to customize the headers send to the server.
options.method HTTP_METHODS One of the provided HTTP_METHODS enum types. Has to match the provided type defined in the route.

useApi Props

Param type required Description
url String [x] The api url.
swrOptions Object The useSwr options object more information on that can get read at: SWR Docs
typeParam type The type of the route handler function. This is used to define the return type of the hook.

useMutateApi Props

Param type required Description
url String [x] The api url.
swrOptions Object The useSwrMutation options object more information on that can get read at: SWR Docs
typeParam type The type of the route handler function. This is used to define the return type of the hook.

preloadData Props

Param type required Description
url String [x] The api url.

(back to top)


Provided enums

HTTP_METHODS

Key Value
DELETE DELETE
GET GET
OPTIONS OPTIONS
PATCH PATCH
POST POST
PUT PUT

HTTP_STATUS

Key Value
CONTINUE 100
SWITCHING_PROTOCOLS 101
PROCESSING 102
EARLY_HINTS 103
OK 200
CREATED 201
ACCEPTED 202
NON_AUTHORITATIVE 203
NO_CONTENT 204
RESET_CONTENT 205
PARTIAL_CONTENT 206
MULTI_STATUS 207
ALREADY_REPORTED 208
IM_USED 226
MULTIPLE_CHOICES 300
MOVED_PERMANENTLY 301
FOUND 302
SEE_OTHER 303
NOT_MODIFIED 304
TEMPORARY_REDIRECT 307
PERMANENT_REDIRECT 308
BAD_REQUEST 400
UNAUTHORIZED 401
PAYMENT_REQUIRED 402
FORBIDDEN 403
NOT_FOUND 404
METHOD_NOT_ALLOWED 405
NOT_ACCEPTABLE 406
PROXY_AUTH_REQUIRED 407
REQUEST_TIMEOUT 408
CONFLICT 409
GONE 410
LENGTH_REQUIRED 411
PRECONDIRTION_FAILED 412
PAYLOAD_TOO_LARGE 413
URI_TOO_LONG 414
UNSUPPORTED_MEDIA_TYPE 415
RANGE_NOT_SATISFIABLE 416
EXPECTATION_FAILED 417
IM_A_TEAPOT 418
MISDIRECTED_REQUEST 421
UNPROCESSABLE_CONTENT 422
LOCKED 423
FAILED_DEPENDENCY 424
TOO_EARLY 425
UPGRADE_REQUIRED 426
PRECONDITION_REQUIRED 428
TOO_MANY_REQUESTS 429
REQUEST_HEADER_FIELDS_TOO_LARGE 431
UNAVAILABLE_FOR_LEGAL_REASONS 451
INTERNAL_SERVER_ERROR 500
NOT_IMPLEMENTED 501
BAD_GATEWAY 502
SERVICE_UNAVAILABLE 503
GATEWAY_TIMEOUT 504
HTTP_VERSION_NOT_SUPPORTED 505
VARIANT_ALSO_NEGOTIATES 506
INSUFFICIENT_STORAGE 507
LOOP_DETECTED 508
NOT_EXTENDED 510
NETWORK_AUTHENTICATION_REQUIRED 511

(back to top)


Support

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

(back to top)

Readme

Keywords

none

Package Sidebar

Install

npm i @nfq/typed-next-api

Weekly Downloads

102

Version

1.5.1-beta

License

MIT

Unpacked Size

151 kB

Total Files

28

Last publish

Collaborators

  • nfqdbt