0.0.15 • Published 1 year ago

@pocketgems/app v0.0.15

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
1 year ago

Todea App Library

Todea App library is a backend framework designed to streamline Node.js development workflow for your side project, then seamlessly transition to enterprise scale applications supporting millions of users.

Topics

Key Features

  • High level DB library
    • For AWS DyanmoDB
    • Schemaful
    • Transactional
    • Scalable
    • Lighting fast
    • Support DAX, Index, Query, Scan and more
  • High level API library
    • Routing
    • Input, output schema
    • Exceptions
    • CORS
    • Authentication
    • Compression
    • Health check API
    • Advanced error handling
  • SDK Generation
    • Swagger UI
    • OpenAPI SDK
    • AWS SDK & CLI compatible

Getting Started

With Todea App library, you can define database schemas, create several APIs, and have a procedurally generated SDK ready for client and frontend consumption within hours.

Creating A Database Table

You can create a database table with a few lines of code:

const db = require('@pocketgems/dynamodb')
const S = require('@pocketgems/schema')

class Order extends db.Model {
  static KEY = {
    id: S.str.min(10).desc(`A unique string id for the order.
      ID must be at least 10 characters long`)
  }

  static FIELDS = {
    customerId: S.str,
    items: S.map.value(S.obj({
      itemId: S.str.desc('An item\'s ID'),
      quantity: S.int.min(1),
      dollarPrice: S.double.min(1)
    }))
  }
}

The example above defines a db table called Order to store customers orders. Each order can have one or more items in it, with each item have an ID, quantity and dollar price. The example uses 2 dependencies for defining schema and data model. You may read more about these libraries in greater detail in their perspective project later:

Creating An API

You can create an API to look up order details like this:

const { TxAPI, EXCEPTIONS: { NotFoundException } } = require('@pocketgems/app')

class GetOrderAPI extends TxAPI {
  static PATH = '/getOrder'
  static DESC = `Get an order by ID, if order doesn't exists a 404 Not found
    error is returned`

  static INPUT = Order.KEY
  static OUTPUT = {
    order: Order.Schema
  }
  static EXCEPTIONS = {
    NotFoundException
  }

  async computeResponse ({ tx, body }) {
    const order = await tx.get(Order, body.id)
    if (!order) {
      throw new NotFoundException()
    }
    return { order: order.toJSON() }
  }
}

You can read more about API interface here.

Creating An App

To create a Todea app, you have to call makeApp like this:

const { makeApp } = require('@pocketgems/app')

const components = {
  Order,
  GetOrderAPI
}
module.exports = makeApp({
  service: 'unittest',
  components,
  cookie: {
    secret: 'unit-test'
  },
  logging: {
    reportErrorDetail: true, // process.env.NODE_ENV === 'localhost',
    unittesting: true, // process.env.NODE_ENV === 'localhost',
    reportAllErrors: true // process.env.NODE_ENV !== 'prod'
  },
  awsC2j: {
    version: '2020-02-20',
    displayName: 'Unit Test',
    signatureVersion: 'v4',
    globalEndpoint: 'todea.example.com',
    globalHeaders: ['x-app', 'x-uid', 'x-admin', 'x-token']
  },
  swagger: {
    disabled: false,
    authHeaders: ['x-app', 'x-uid', 'x-admin', 'x-token'],
    servers: ['http://localhost:8080'],
    routePrefix: '/app/docs'
  }
})

Starting Server

The makeApp() helper method creates a fastify instance with a few plugins loaded already. You may customize the fastify instance further using fastify's customization features. To start the app, you have to call .listen() according to Fastify's documentation. For example, makeApp() is called in a app.js file, and the returned promise is exported, then you write the following code to create a server:

require(pathToApp)
  .then(app => app.listen({ port: 8090, host: '0.0.0.0' }))
  .catch((err) => {
    console.log(err)
    process.exit(1)
  })

Components

Todea app is composed of components. A Todea app component can be an API, DB Table (db.Model), S3 bucket etc. These components are passed to makeApp() in an object as components. For example, components may be initialized like this:

const components = {
  Order,
  GetOrderAPI
}

makeApp({
  components,
  ...
})

Internally, each of these components get registered to a corresponding system. For example, API is registered with fastify as a route, while db table is registered with AWS DynamoDB service, or as an AWS CloudFormation Resource if Infrastructure-As-Code is required.

Customizing Component Registration

The component system uses a visitor pattern to allow extending the registration workflow with custom components. For example, to add a new type of component ExampleComponent, you need to do the following: 1. Subclass ComponentRegistrator, and add a registerExampleComponent (exampleComponent) method

const { ComponentRegistrator } = require('@pocketgems/app')

class CustomComponentRegistrator extends ComponentRegistrator {
    registerExampleComponent (exampleComponent) {
        // do what needs to be done
    }
}
  1. You can pass the new CustomComponentRegistrator class to makeApp() like this
    makeApp({
        RegistratorCls: CustomComponentRegistrator
    })
  2. Implement static register (registrator) in the new ExampleComponent class
    class ExampleComponent {
        static register (registrator) {
            registrator.registerExampleComponent(this)
        }
    }
  3. Pass the new type of component as part of components like this makeApp({ components: { ExampleComponent } })

Unit testing

Setup

Todea apps store data in AWS DynamoDB service. You must setup the database before running tests. You can run yarn --cwd ./node_modules/@pocketgems/app setup; yarn --cwd ./node_modules/@pocketgems/app start-local-db to start a local AWS DynamoDB docker instance. And use @pocketgems/app/test/environment.js to setup Jest envrionments.

Writing Tests

This library exposes @pocketgems/unit-test through @pocketgems/app/test/base-test and adds 2 new symbols:

  • BaseAppTest is the common base class for testing APIs. Useage is similar to BaseTest, but exposes a this.app handle to invoke APIs.

    You have to implement a static async requireApp() method to import the app.

  • mockGot is a utility method to mock out-going requests from this.callAPI(). See examples of using this in this library's unit tests.

Generating SDKs

Swagger UI

This library generates an interactive Swagger UI for all APIs at /service/docs.

OpenAPI SDKs

You can export APIs in an OpenAPI schema from /service/docs/json, and use that with OpenAPI / Swagger SDK to generate SDKs in any supported languages.

CAUTION: Swagger SDKs use postional arguments in all SDKs, maintaining backward compatibility will be challenging with vanilla SDK generators. You may customize the generators to pass keyword arguments instead for languages that support it.

AWS SDKs

This library adds REST APIs to generate C2J schemas. It is what AWS uses to generate their SDKs and CLI. One set for public APIs and one set for private APIs. In the following lines group can be service, admin or user.

  • /service/c2j/group/uid: Returns the API version, e.g. example-2020-09-20.
  • /service/c2j/group/normal: Returns the normal C2J schema. This schema contains API definition and documentations.
  • /service/c2j/group/api: Returns normal C2J schema minus any documentation.
  • /service/c2j/group/docs: Returns a docs C2J schema.

If there is no API to be exported, these endpoints will return empty string as the response.