deno-api-server

An http/rest api server for deno. Based on std deno http library and use the concept of functional programming for your endpoint definitions.

Basic functionality example

import { Api, Route, EMethod } from "https://deno.land/x/deno_api_server/mod.ts";

const api = new Api({ port: 8080 });

api.addRoute(
        new Route(EMethod.GET, '/')
            .addPipe(({response}) => {
                response.body = {message: 'Hello API'};
            })
    )

console.log(`Start server localhost:${api.serverConfig.port}`);
await api.listen();

Last Feature implementations

  • add file handling
  • add functionality for breaking pipe process
  • add new example to handle files File example

More code examples

Add simple route to your api

/** INFO: don't forget route to your Api, see first example **/
new Route(EMethod.GET, '/say-hello')
    .addPipe(({response}) => {
        response.body = {message: 'Hello API'};
    })
)

Add simple route with multi methods

/** INFO: don't forget route to your Api, see first example **/
new Route([EMethod.GET, EMethod.POST], '/say-hello')
    .addPipe(({response, request}) => {
        response.body = { message: `Hello API by ${request.method}` };
    });

Use state to pass data between pipes

/** INFO: don't forget route to your Api, see first example **/
new Route(EMethod.GET, '/state')
    .addPipe(({response, state}) => {
        const before = new Date();
        response.body = {before};
        state.set('called', before);
    })
    .addPipe(({response, state}) => {
        Object.assign(response.body, {
            passed: state.get('called'),
            done: new Date()
        })
    });

Throw error to handle by api

/** INFO: don't forget route to your Api, see first example **/
new Route(EMethod.GET, '/error')
    .addPipe(() => {
        throw new RequestError('I dont like', 400);
    })

Key mapped uri with KeyMatch for url params

new Route(
    EMethod.GET, 
    new KeyMatch(
        '/get-by-key-name/:id/:name',
        {
            id: { type: Number },
            name: {}
        }
    )
)
    .addPipe(({response, match}) => {
        const { params } = match;
        response.body = { message: `You call with keymatch`, id: params.get('id'), name: params.get('name') };
    })

For more details about KeyMatch see API Documentation KeyMatch

API Dokumentation

class Api

Core server class, create an instance for your own api

Similar configuration of deno http service

new Api(config: IServerConfig)

Add new Routes / Entpoints

api.addRoute(route: IRoute)

Start server to listen

api.listen();

class Route

Core route or entpoint defintion for your api.

new Route(method: string[] | string, uri : IMatcher | string)

Add route pipeline

route.addPipe(pipe: IPipe) : Route

Example: Add a stack of pipes

route
    .addPipe((context: IContext) => { ... })
    .addPipe(async (context: IContext) => { ... })

Tip use deconstruction for select context props

Execute a route pipeline, this will be called by api if route match with uri defintion

route.execute(url: URL, request: ServerRequest, response: IResponse): Promise<IContext>

Matcher

class UriMatch

Base endpoint matcher for simple definition of uris.

INFO you can use a string for Route definition, the Route will create a simple UriMatch

class KeyMatch

More complex uri definition for your route with transpile your parameters. A KeyMatch can use a transform function to change the type of value

new KeyMatch(uri: string, describe: IKeyDescribes)

Examples:

new KeyMatch('/get/:id', { id: {} }) => match: /get/1, params: { id: '1' }
new KeyMatch('/get/:id', { id: {} }) => match: /get/hello-my, params: { id: 'hello-my' }

// with transpile type
new KeyMatch('/get/:id', { id: { type: 'number' } }) => match: /get/1, params: { id: 1 }
new KeyMatch('/get/:id', { id: { type: 'number' } }) => not match: /get/hello

// with rest type
new KeyMatch('/get/:path', { path: { type: 'rest' } }) => match /get/super-dup/megaman, params: { path: 'super-dup/megaman' }

Info for KeyMatch

Use EPatternTypes for param type or you can extend it with extendPatternMap function. See src/definition/pattern-map.ts

IKeyDescribe.type

  • 'Any' for any parameter like string, number and combination
  • Number for float and numbers
  • 'Int' for integer numbers

Quick test api server

import "https://deno.land/x/deno_api_server@v0.0.2/example.ts";
/**
 * will start server on localhost:8080
 * try all entpoints on example
 **/

All presets defined in src/presets

List of presets

Preset API Documentation

All presets defined in src/presets

Route presets src/presets/routes/*

  • healthz GET|HEAD /healthz for healthcheck
  • status GET /status similar to healthcheck but customizable definitions, see API DOC

Pipes presets src/presets/pipes/*

body/raw-body.pipe.ts

Read body data of request.

Set state body with string data Set state bodyType with the type of data == raw

import rawBodyPipe from 'https://deno.land/x/deno_api_server/src/presets/raw-body.pipe.ts';
route
    .addPipe(rawBodyPipe)
    .addPipe(({ state } : IContext )=> {
        const anyBody = state.get('body');
    }); 

body/json-body.pipe.ts

Try to parse json data of request body. Otherwise throw 400 error.

Set state body with json data Set state bodyType with the type of data == json

import jsonBodyPipe from 'https://deno.land/x/deno_api_server/src/presets/json-body.pipe.ts';
route
    .addPipe(jsonBodyPipe)
    .addPipe(({ state } : IContext )=> {
        const validJsonBody = state.get('body');
    }); 

process/redirect.pipe.ts

Redirect pipe with default 302 redirection to your url. Basic implementation to redirect your route.

import redirectPipe from 'https://deno.land/x/deno_api_server/src/process/redirect.pipe.ts';
route
    .addPipe(redirectPipe('/other-page'))

If you want to redirect a dynamic page use any capsule pipe function, like here

import redirectPipe from 'https://deno.land/x/deno_api_server/src/process/redirect.pipe.ts';
route
    .addPipe((context: IContext) => {
        const url = `/test-to-my?set=1`
        return redirectPipe(url)(context);
    })

Testing

Use buildin mocks to create a api request.

Build-In API Documentation

A simple example

import { mockApi } from 'https://deno.land/x/deno_api_server/dev_mod.ts';

Deno.test('Example mockApi route success request', async () => {
    const route = new Route('GET', '/hello');

    // create api
    const api = mockApi(route);

    await api.sendByArguments('GET', '/hello');

    assertEquals(api.lastRoute === route, true);
    assertEquals(api?.lastContext?.response.status, 200);
})

A payload example

import { mockApi, mockRequest } from 'https://deno.land/x/deno_api_server/dev_mod.ts';

Deno.test('Example mockApi post request with request data', async () => {
    const route = new Route('POST', '/submit');
    route
        .addPipe(jsonBodyPipe)
        .addPipe(({state, response}) => {
            response.status = 201;
            response.body = state.get('body');
        });

    const api = mockApi(route);

    const request = mockRequest('POST', '/submit', {
        name: 'super'
    });
    await api.sendByRequest(request);

    assertEquals(api.lastRoute === route, true);
    assertEquals(api?.lastContext?.response.status, 201);
    assertEquals(api?.lastContext?.response.body, { name: 'super' });
});

More examples in example/unit-testing.test.ts

Best Practice

Tips and best practice to use this server.

  • Write custom and small pipes to reuse
  • create a custom createRoute function to setup your app
  • use Route.di / Route.injections to share services and other stuff
  • create factory functions for injections, like getConnection factory to connect to database only if route will execute
  • write test by mock your injections (like mock service for your database connection)
  • use state to pass data between your pipes

examples

See example folder for more use case examples.

example/unit-testing.test.ts

route and api testing example and how to use build-in mock functions.

Third party examples are

example/body-validation.ts

Use validasaur to validate your json body validasaur deno.land

example/authentification-jwt.ts

Use djwt to handle and secure your endpoints, a very simple example to customize your process validasaur deno.land

deno