This project is aimed to give the world a comprehensive OOP solution for creating a full-stack WEB applicaiton, following a pure layered architecture design, the power of NodeJS and EO principals.
IMPORTANT: Note, that this project is yet under active development.
Beri - from the Russian word “Бери́”, which means “take (it)” in English.
It's pronounced with the emphasis on the last syllable like:“be-REE”
, .
- Object-Oriented
Everything is an object with clear responsibilities and encapsulation first approach - Immutability First
Objects are immutable by default, reducing side effects and improving predictability - Null Values
Here's a tricky one point, since thenull
is a valid primitive value that represents the intentional absence of any object value we denote that thenull
primitive value can be used properly in situations where we need to demonstratenullable
object of any/or specified form. - Declarative Over Imperative
Express what should happen, not how it should happen - Composition Over Inheritance
Build composable objects instead of extending others
Creating prototype chains is considered allowed
- JSON schemas support
Use JSONSchema protocol to create and use your schemas for routing, data exchanges, buiolt-in validation. - Clean layered architecture
Domain layer, API (routes) layer, DB-access layer, Presentors layer (UI)
- Hot refresh
Modify your code and see changes immediately - Component-Based structure
Build UIs with reusable, composable components - Built-in Routing
Declarative API routing based on metaprogramming and schemas - Server-Side Rendering
First-class support for SSR - RESTful & RPC API Support
Easy creation of RESTful and RPC endpoints (HTTP, WS) - Template Engine Integration
Support for most popular templating engines - Static Resource Handling
Efficient serving of static assets
Create a simple Beri application server.
import { App } from '@beri/core';
import { Routes, HTTPRoute } from '@beri/routes';
const app = new App(
new Routes(
new HTTPRoute('/', () => 'Hello, World!')
),
{ httpPort: 3000 }
);
app.start();
// You can define a JSON routing schema for HTTP routes
const usersSchema = {
method: 'GET',
url: '/users/:id',
schema: {
request: {
params: {
type: 'object',
properties: {
id: {
type: 'number',
},
},
},
},
response: {
200: {
type: 'object',
properties: {
resp: {
type: 'string',
}
},
required: ['resp']
},
},
},
};
// Also you can define a JSON schema for WS routes
const loginSchema = {
type: 'call',
method: 'auth/login',
schema: {
message: {
type: 'object',
properties: {
token: {
type: 'string',
}
},
}
reply: {
type: 'object',
properties: {
code: 200,
ok: {
type: 'boolean',
}
},
required: ['ok']
},
},
};
And then use your web-server in more somprehensive way
import { App } from '@beri/core';
import { Routes, HTTPRoute, WSRoute } from '@beri/routes';
import { SecuredRoute } from '@beri/secure';
const app = new App(
new Routes(
new HTTPRoute(
new SecuredRoute(
usersSchema,
(req) => ({ resp: `User ID: ${req.params.id}` })
)
),
new WSRoute(
new SecuredRoute({
route: {
loginSchema,
(socket) => socket.send({
ok: true,
}),
},
type: 'access_token',
})
)
),
{ httpPort: 3000, wsPort: 3001 },
);
app.start();
For a static file serving use the object representing the content
import { Route } from '@beri/routes';
import { StaticContent } from '@beri/static';
const app = new App(
// ...
new Route(
'/docs/*',
new StaticContent('./docs')
)
);
Define your UI block
// src/ui
export class UserProfile {
constructor(user) {
this.user = user;
};
render() {
return {
div: {
className: 'user-profile',
styles: // ...
p: `Name: ${this.user.name}`,
p: `Email: ${this.user.email}`,
},
};
};
};
Usage
// src/server.js
import { UIComponent } from '@beri/ui';
const app = new App(
// ...
new Route(
'/profile',
(req) => {
const user = // get a user data from db;
return new UIComponent(
new UserProfile(user)
).render();
}
);
);
Considering a complex UI we use the same approach of composable objects here too:
import { Page } from '@beri/ui';
const app = new App(
// ...
new Route(
'/profile',
(req) => {
// ...
return new Page(
new UserProfile(user),
new SettingsMenu(
new SettingsTabs(),
settingsSections, // maybe a plain JS object as well
),
new SupportChat(url, options),
).render();
}
);
);
You can use a Beri
application only as a web-server, if you wish, meaning be able to use its HTTP or WS API as an external API endpoints. Or when you need to create a backend only with no UI for some 3rd-party clients.
...
The main objective is to use it like a constructor of composable objects, that means whenever we'd like to add new functionality, a feature, an action or something - we use existing and create new objects.
When we need to establish a new type of behavior - we create a decorator object, which should take any inner sub-objects to construct a working block.
See more at my blogpost - http://gadzhievislam.org/Programming/Decorators%20and%20composition/
An example of that maybe this illustration of a Client's agreement registration:
new Registration(
new ClientAgreement(
new CompanySignedAgreement(
new PatternDraftedFile(
new File('UserAgreementData.xml')
).draft()
.toPDF()
).validate()
).sign()
).proceed()
This example shows what is the principle of composing objects into one block of behavior pattern - we can see from the code sample, that a Registration process has been declared in a declarative way where all that belongs to it is right in front of our eyes.
And we can dive deep into it just by exploring each object, nothing is hidden from us as deevlopers.