Skip to main content
@rhtml Logo

Build Modern Web Apps with @rhtml

The reactive, declarative framework for building scalable web applications. Combine the power of Web Components, RxJS, and dependency injection in one elegant solution.

See @rhtml in Action

Experience the power of functional reactive components

app.component.ts
import { Component, DefineDependencies } from '@rhtml/component';
import { Container, Injectable } from '@rxdi/core';
import { html, LitElement, property } from '@rxdi/lit-html';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
class CounterService {
  counter = 55;
}

@Component({
  Settings: {
    selector: 'app-root',
  },
  Providers: DefineDependencies(CounterService)(Container),
  State: function(this: AppComponent, [counterService]) {
    return this.subject.pipe(
      map(value => ({
        counter: value + counterService.counter
      }))
    );
  },
  Render: ([counterService]) =>
    function(this, { counter }) {
      return html`
        <p>Count: ${counter}</p>
        <button @click=${() => this.increment()}>
          Increment
        </button>
      `;
    }
})
export class AppComponent extends LitElement {
  @property({ type: Number })
  private count = 0;

  subject = new BehaviorSubject(0)

  increment() {
    this.subject.next(this.count++);
  }
}

Why Choose @rhtml?

Modern web development made simple, powerful, and maintainable

⚑

Lightning Fast

Built on Web Components and LitElement for optimal performance. Minimal overhead with maximum efficiency.

πŸ”„

Reactive by Design

Native RxJS integration for reactive programming. Build responsive UIs with declarative data flows.

🎯

Type Safe

Full TypeScript support with excellent IDE integration. Catch errors at compile time, not runtime.

🧩

Component Based

Build reusable, composable components with Web Components standards. Share components across projects easily.

πŸ’‰

Dependency Injection

Powerful DI container inspired by Angular. Manage services and dependencies with ease.

πŸš€

Full Stack

Frontend and backend in one framework. Fastify integration with GraphQL, MongoDB, and AMQP support.

More Examples

Real examples showing the power and simplicity of @rhtml

πŸ”„ Reactive State Management

counter.component.ts
import {
  Component,
  DefineDependencies
} from '@rhtml/component';
import { Container, Injectable } from '@rxdi/core';
import { html, LitElement, property } from '@rxdi/lit-html';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
class CounterService {
  counter = 55;
}

@Component({
  Settings: {
    selector: 'counter',
  },
  Providers: DefineDependencies(CounterService)(Container),
  State: function(this: CounterComponent, [counterService]) {
    return interval(1000).pipe(
      map((value) => ({
        counter: this.counter + counterService.counter + value,
      }))
    );
  },
  Render: ([counterService]) =>
    function(this, { counter }) {
      return html`
        <div>
          <h2>Counter: ${counter}</h2>
          <p>Service Counter: ${counterService.counter}</p>
        </div>
      `;
    }
})
export class CounterComponent extends LitElement {
  @property({ type: Number })
  counter: number = 0;
}

🎯 Backend API with Fastify

user.controller.ts
import {
  Controller,
  Route,
  Subscribe
} from '@rhtml/fastify';
import { Subscribe } from '@rhtml/amqp';
import { MessageService } from './message.service';

@Controller()
export class NotificationController {
  constructor(private messageService: MessageService) {}

  @Route({
    url: '/publish-message',
  })
  async sendNotification(notification: any) {
    await this.messageService.publish('my-message-queue', {});
  }

  @Subscribe({
    queue: 'my-message-queue',
    consumeOptions: {
      noAck: false,
    },
  })
  async myMessageQueueSubscription(
    message: ConsumeMessage,
    channel: AmqpChannel
  ) {
    channel.ack();
  }
}

Build scalable backend APIs with @rhtml/fastify and decorators. Get started quickly with the fastify-starter template.

πŸ“Š GraphQL Integration

user-list.component.ts
import {
  Component,
  DefineDependencies
} from '@rhtml/component';
import { Container, Injectable } from '@rxdi/core';
import { html, LitElement, property } from '@rxdi/lit-html';
import { of } from 'rxjs';

@Injectable()
class UserService {
  getUser(userId: string) {
    return {
      id: userId,
      name: 'John Doe',
      email: 'john@example.com'
    };
  }
}

@Component({
  Settings: {
    selector: 'user-list',
  },
  Providers: DefineDependencies(UserService)(Container),
  State: function(this: UserComponent, [userService]) {
    return of({
      user: userService.getUser(this.userId),
      time: new Date().getSeconds(),
    });
  },
  Render: ([userService]) =>
    function(this, { user, time }) {
      return html`
        <div>
          <h2>${user.name}</h2>
          <p>Email: ${user.email}</p>
          <p>Current time: ${time}</p>
        </div>
      `;
    }
})
export class UserComponent extends LitElement {
  @property({ type: String })
  userId = '';
}

πŸ’Ύ MongoDB with Mongoose

user.service.ts
import { Injectable, Inject } from '@rhtml/di';
import {
  AmqpChannel,
  AmqpService,
  ConsumeMessage
} from '@rhtml/amqp';

@Injectable()
export class MessageService {
  constructor(private amqpService: AmqpService) {}

  async publish(name: string, payload: any) {
    await this.amqpService.publish(name, {
      payload: {},
    });
  }
}

@Injectable()
export class UserService {
  constructor(
    @Inject(Repositories) private repos: Repositories,
    private messageService: MessageService
  ) {}

  createFile(file: File) {
    return this.repos.file.create(file);
  }

  listFiles() {
    return this.repos.file.find();
  }

Component System Comparison

Choose the right approach for your project

@rxdi/lit-html

First Generation
simple-counter.ts
import { Component, html, LitElement, property } from '@rxdi/lit-html';

@Component({
  selector: 'simple-counter',
  template: () => html`
    <div>
      <h2>Count: ${this.count}</h2>
      <button @click=${this.increment}>
        +1
      </button>
    </div>
  `
})
export class SimpleCounter extends LitElement {
  @property({ type: Number })
  count = 0;

  increment() {
    this.count++;
  }
}
βœ“ Pure Web Components
βœ“ Simple API
βœ“ Maximum flexibility
βœ“ Direct LitElement inheritance

@rhtml/component

Second Generation
reactive-counter.ts
import { Component, DefineDependencies } from '@rhtml/component';
import { Container, Injectable } from '@rxdi/core';
import { html, LitElement, property } from '@rxdi/lit-html';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
class CounterService {
  counter = 55;
}

@Component({
  Settings: {
    selector: 'reactive-counter',
  },
  Providers: DefineDependencies(CounterService)(Container),
  State: function(this, [counterService]) {
    return interval(1000).pipe(
      map((value) => ({
        counter: this.counter + counterService.counter + value,
      }))
    );
  },
  Render: ([counterService]) =>
    function(this, { counter }) {
      return html`
        <div>
          <h2>Counter: ${counter}</h2>
          <p>Service Counter: ${counterService.counter}</p>
        </div>
      `;
    }
})
export class ReactiveCounter extends LitElement {
  @property({ type: Number })
  counter: number = 0;
}
βœ“ Functional Reactive
βœ“ Dependency Injection
βœ“ Observable State
βœ“ Advanced Composition

Ready to Build Something Amazing?

Join the community of developers building modern web applications with @rhtml