Introducing OrchaJS: A TypeScript-Native Client-Server Facilitator

Jeremy C. Zacharia
JavaScript in Plain English
6 min readApr 12, 2021

--

Facilitate seamless communication between client and server; not only at run-time but throughout the entire development process.

Without further ado, here is the repo: https://github.com/jczacharia/orcha
Note: within the repo contains a fully-featured example todo app.
Also, here is the user authentication starter repo you can use for starting your own Orcha projects: https://github.com/jczacharia/orcha-user-auth-starter

What is OrchaJS?

OrchaJS is a framework to safely, securely, and efficiently build scalable web apps requiring real-world, highly relational domain modeling and essential features such as user authentication, file uploading, pagination, and real-time updates. All while providing an unprecedented developer experience.

In a nutshell, OrchaJS uses a project-specific monorepository to house your project’s frontend and backend. This means that domain modeling, business rules/calculations, and communication protocols are held in a shared folder that both the client and server libraries can use, creating a single source of truth for your project’s domain. This in turn facilitates robust communication between the client and server on all stages of development and production.

Before we jump into the technical details, I want to state its limitations so you know what to expect.

Limitations

Since OrchaJS is written in TypeScript, this means that only TypeScript libraries and frameworks can be used. Also, since OrchaJS is a framework, it is best suited for new projects. The current compatible technologies are (but not limited to):

Diving Right In

When starting a new project, it's advisable to first layout domain models and their relationships. Using Orcha’s advanced TypeScript generics, domain models are created using One-to-One, One-to-Many, Many-to-One, and Many-to-Many relations:

Example for Orcha Domain Modeling. Link To Example Project Domain Models

Note: Orcha includes a IManyToMany type, however, is not recommended to directly use Many-to-Many relationships but instead, create a separate entity that links the two entities together by using One-to-Many and Many-to-One relations (shown above). This has the added advantage of adding columns to your linked entities.

Notice how each relational property describes its relationship to another entity in two ways:

  • The first template parameter is the relational entity,
  • and the second template parameter is the property on the relational entity that points back to the entity.

For example, the todos property in the User model describes a one-to-many relationship with the user property on the Todo model:

// On User model
todos: IOneToMany<Todo, 'user'>;
// One user has many todos.// On Todo model
user: IManyToOne<User, 'todos'>;

Once you have established a single source of truth for your domain models, you can freely use these models in the frontend. For the backend, there is one more step to create the entities to use in your database. Here is an example using TypeORM for creating a UserEntity and UserRepository:

Settings up Entity and Repository Link to Files

Note the IOrchaTypeormRepository base-class implements all built-in functions needed to easily read and write data to the database.

Once your domain models have been described, you can create an Orchestration to define the Operations that you want to execute on your entities.

What is an Operation?

An Operation exposes the server to the outside world via an HTTP POST endpoint. Each Operation should describe an action. This action can either read or mutate data.

What is an Orchestration?

An Orchestration groups related Operations under a single parent endpoint. An endpoint looks as follows: /orcha/<orchestration name>/<operation name>.

Todo Orchestration Example. Link To File

The first template parameter in IOperation is the base entity or schema you want to return from that Operation. The second template parameter is the data transfer object (DTO) that describes the Operation parameters needed to execute. DTOs also include validation to ensure data integrity.

Validating DTO example. Link To File

By describing your domain models and Operations like this, you can create an Orcha Query that defines the entity schema to be returned from an Operation including any desired relational joins.

What is an Orcha Query?

An Orcha Query is a TypeScript-based simple, yet effective way to describe the data you want returning from a given Operation, including joining relational data on the database. Each Orcha Query consists of key-value pairs where each key corresponds to either a true value (denoting a primitive field on an entity) or an object (denoting a relationship to another entity).

When creating an Orcha Query, the developer gets strict typing and convenient Intellisense over the data that they want:

Describing the response schema for Todo Operations using our Many-to-Many example. Link To File

After describing the data schema returning from a given operation, then comes setting up the client and server Orchestrations:

Client Orchestration. Link To File
Server Orchestration (Unsecure). Link To File

Note that token is an extra field that is added via a client interceptor. See here. You can also see the general HTTP body schema for a generic Operation here.

Wait a minute, you said that these query objects are sent from the client to join relational data on the database and returned from the server, doesn’t that mean that a bad actor can inject extraneous data in the queries to get sensitive data?

That is right, however, OrchaJS comes with a security feature that prevents this. Since Orcha Queries are held in a shared folder between the client and server, the query sent from the client can be validated on the server and be checked for any extraneous data such as keys and values.

Here is our example utilizing query validation:

Server Orchestration (Secure). Link To File

If a bad actor tried to inject another key into the query object, then an unauthorized error will be thrown. In this way, private data is easily controlled.

Okay, then how does this tie into the server business logic?

Calls made from ClientOrchestration will be received on the ServerOrchestration. Once a call is received on a server Operation, the Query, token (if needed), and DTO is sent to the business logic part of the server. Here is an example in the Todo service of reading and updating Todo entities:

Todo Service. Link to File

Notice how you can use Orcha Queries directly in repository methods (like the second argument of this.todo.findOneOrFail) to get the convenience of Orcha throughout the development process.

What about making calls from the client-side services?

Simply use the TodoOrchestration via dependency injection then initiate the desired Operation.

Example Client Operation Calls via Service Link To File

The Orcha Query TodoQueryModel is parsed via TypeScript generics so Intellisense will tell you the expected type and, additionally, the compiler will throw a compile-time error if you used any omitted fields anywhere else in your client app.

Integration Testing

Orcha comes with integration testing out-of-the-box. Making calls is the same as using the Client Orchestration but with the added token field. Here is an example with user authentication:

Link To File

Nice! If I want to create a scalable web app using this framework, what does the high-level architecture look like?

Here is an example of a best-practices folder structure:

Describing each folder bottom-up:

shared/util houses all project-agnostic, platform-agnostic code.

shared/domain houses all models, queries, business rules/calculations, Orchestration Interfaces, and any other domain-related design data.

server/orcha houses all Server Orchestrations that make calls to the application-services business logic.

server/domain-services houses all entities and their corresponding repositories. See here.

server/application-services houses all of the business logic (the majority of server code will go here).

client/shell houses all routable components.

client/shared/util houses all project-agnostic, client-specific code.

client/shared/ui houses all dumb-components.

client/shared/data-access houses all state-management code (NgRx preferred), Client Orchestrations, smart-components, and any other client-specific domain code.

If your project requires additional features that you want to separate from the client/shell, you can do so by adding a feature folder under client like so:

Why would I choose this over something like GraphQL?

Since OrchaJS is written completely in TypeScript, you get full type-safe features throughout the entire development process without any need for code-generation or unreliable Intellisense.

Also, Orcha queries are not only used between client and server but communicating directly to your database via Orcha Repositories, giving the ability to ask your database for exactly the data you want including relational data.

Conclusion

If you’ve taken interested in this novel framework then please try it out and give some feedback!

And if you’ve made it this far then you might have some interest in becoming a contributor! OrchaJS is currently developed and maintained by me but I am looking for developers to help develop, maintain, and write documentation and integration tests.

Cheers!

More content at plainenglish.io

--

--