DEV Community

Cover image for PURISTA - a modern typescript framework for IoT/edge, microservices, and serverless
Sebastian Wessel
Sebastian Wessel

Posted on

PURISTA - a modern typescript framework for IoT/edge, microservices, and serverless

There are a lot of frameworks available in the JavaScript/typescript ecosystem.

PURISTA is more than a classical framework for building REST APIs.
It focuses on two big topics.

Building the logic behind the endpoints fast & efficiently in a product(ion) ready way.

Enable the project to not only scale from a technical perspective. The project will be enabled to scale as a project with more people, more features, and more requirements.

The idea

PURISTA adapts the idea of independent, small functions from serverless and FaaS.

These single functions are connected via a message-based approach, which also adapts event-driven patterns.

How messages are exchanged between separated functions and how single functions are grouped or deployed is a free choice.
It also does not matter for the implementation of the functions.

The benefits

Because of this simple idea, it becomes possible to:

  • orchestrate and deploy the functions as one large monolith
  • group them by domains and deploy them as microservices
  • deploy them as single serverless functions (FaaS)
  • mix up the orchestration and deployment
  • free choice underlying infrastructure and vendor
  • scale work across available "human pool"
  • add features fast like spikes or PoCs

The example

A more detailed example and walkthrough can be found on the website PURISTA handbook. Here is a ruff overview how it works:

mkdir example
cd example
npx @purista/cli init
Enter fullscreen mode Exit fullscreen mode

Create a service, which is a logical, versioned group of functions.

purista add service
Enter fullscreen mode Exit fullscreen mode

Create your first command:

purista add command
Enter fullscreen mode Exit fullscreen mode

This will output something like this

🎉 The command "sign up" in service "user" version1 is created 🎉


start adding your business logic here:
./src/service/user/v1/command/signUp/signUpCommandBuilder.ts
Enter fullscreen mode Exit fullscreen mode

In the mentioned file /src/service/user/v1/command/signUp/signUpCommandBuilder.ts you will add the business logic. In the same file, you can also add more information. Like how this command might be exposed.
In the same directory, you will find the prepared schema files, the unit test file, and a type file.
Here, you only need to align the schema according to the input and output for your function. Typescript types will be automatically generated.

Create your first subscription:

purista add subscription
Enter fullscreen mode Exit fullscreen mode

This will basically generate a similar structure and similar files.
The difference between commands and subscriptions is, that commands are invoked by someone who is expecting a return value.
Subscriptions are a passive part, which is listening for messages (events). They can also emit their own events as results, which might be consumed by other subscriptions.

Adding functions is quite simple. But it is also simple to get them running.
As an example, you might want to deploy them as microservices. This would be some index.ts file like this:

import { AmqpBridge } from '@purista/amqpbridge'
import {
  getNewInstanceId,
  gracefulShutdown,
  initLogger,
} from '@purista/core'
import { getHttpServer } from '@purista/k8s-sdk'

import { theServiceV1Service } from './service/theService/v1/'

const main = async () => {
  // create a logger
  const logger = initLogger()

  // set up the eventbridge and start the event bridge
  const eventBridge = new AmqpBridge({
    instanceId: process.env.HOSTNAME || getNewInstanceId(),
    config: {
      url: process.env.AMQP_URL,
    },
  })
  await eventBridge.start()

  // set up the service
  const theService = theServiceV1Service.getInstance(eventBridge)
  await theService.start()

  // create http server
  const server = getHttpServer({
    logger,
    // check event bridge health if /healthz endpoint is called
    healthFn: () => eventBridge.isHealthy(),
    // optional: expose the commands if they are defined to have url endpoint
    services: theService,
    // optional: expose service endpoints at [apiMountPath]/v[serviceVersion]/[path defined for command]
    // defaults to /api
    apiMountPath: '/api',
  })

  // register shut down methods
  gracefulShutdown(logger, [
    // start with the event bridge to no longer accept incoming messages
    eventBridge,
    // optional: shut down the service
    theService,
    // stop the http server
    server,
  ])

  // start the HTTP server
  // defaults to port 8080
  // optional: you can set the port in the optional parameter of this method
  await server.start()
}

main()
Enter fullscreen mode Exit fullscreen mode

You can imagine that it is also simply possible to spin up multiple different services in the file to deploy as a monolith.

By simply adding more services and functions, you can quickly build whole systems like those shown in this picture:

PURISTA microservice

There are a lot more things PURISTA will provide. For example:

  • config stores
  • secret stores
  • state stores
  • webserver
  • OpenTelemetry support built into the core of the framework
  • support of different message brokers and vendors

...and much, much more available right now or in the upcoming weeks and months.

While the project is in some early stages and a lot of features will need to come, it is worth having a look at it.

Try it out!

Any feedback, ideas, hints, help, and so on are highly welcome.

PURISTA - official website

You can also join the Discord Channel to get in touch with the maintainers and other developers!

Top comments (0)