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:
- Subclass
ComponentRegistrator
, and add aregisterExampleComponent (exampleComponent)
methodconst { ComponentRegistrator } = require('@pocketgems/app') class CustomComponentRegistrator extends ComponentRegistrator { registerExampleComponent (exampleComponent) { // do what needs to be done } }
- You can pass the new
CustomComponentRegistrator
class to makeApp() like thismakeApp({ RegistratorCls: CustomComponentRegistrator })
- Implement
static register (registrator)
in the newExampleComponent
classclass ExampleComponent { static register (registrator) { registrator.registerExampleComponent(this) } }
- Pass the new type of component as part of
components
like thismakeApp({ 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 fromthis.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.