Skip to content
forked from remult/remult

A CRUD framework for full stack TypeScript

License

Notifications You must be signed in to change notification settings

sashagoebbels/remult

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

What is Remult?

Remult is a full-stack CRUD framework that uses your TypeScript model types to provide:

  • Secure REST API (highly configurable)
  • Type-safe frontend API client
  • Type-safe backend query builder

Remult โค๏ธ Code Sharing

With model types shared between frontend and backend code, Remult can enforce data validation and constraints, defined once, both in the front-end and within back-end API routes.

Getting started

The best way to learn Remult is by following a tutorial of a simple Todo web app with a Node.js Express backend.

Installation

npm i remult

Usage

Setup API backend using a Node.js Express middleware

import express from 'express';
import { remultExpress } from 'remult/remult-express';

const port = 3001;
const app = express();

app.use(remultExpress());

app.listen(port, () => {
  console.log(`Example API listening at https://localhost:${port}`);
});

Define model classes

import { Entity, Fields } from 'remult';

@Entity('products', {
    allowApiCrud: true
})
export class Product {
  @Fields.string()
  name = '';

  @Fields.number()
  unitPrice = 0;
}

๐Ÿš€ API Ready

> curl https://localhost:3001/api/products

[{"name":"Tofu","unitPrice":5}]

Find and manipulate data in type-safe frontend code

async function increasePriceOfTofu(priceIncrease: number) {
  const productsRepo = remult.repo(Product);

  const product = await productsRepo.findFirst({ name: 'Tofu' });
  product.unitPrice += priceIncrease;
  productsRepo.save(product);
}

...exactly the same way as in backend code

@BackendMethod({ allowed: Allow.authenticated })
static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
  const productsRepo = remult!.repo(Product);

  const product = await productsRepo.findFirst({ name: 'Tofu' });
  product.unitPrice += priceIncrease;
  productsRepo.save(product);
}

โ˜‘๏ธ Data validation and constraints - defined once

import { Entity, Fields } from 'remult';

@Entity('products', {
    allowApiCrud: true
})
export class Product {
    @Fields.string<Product>({
        validate: product => {
            if (product.name.trim().length == 0)
                throw 'required';
        }
    })
    name = '';

    @Fields.number({
        validate: (_, field) => {
            if (field.value < 0)
                throw "must not be less than 0";
        }
    })
    unitPrice = 0;
}

Enforced in frontend:

const product = productsRepo.create();

try {
  await productsRepo.save(product);
}
catch (e: any) {
  console.error(e.message); // Browser console will display - "Name: required"
}

Enforced in backend:

> curl https://localhost:3001/api/products -H "Content-Type: application/json" -d "{""unitPrice"":-1}"

{"modelState":{"unitPrice":"must not be less than 0","name":"required"},"message":"Name: required"}

๐Ÿ”’ Secure the API with fine-grained authorization

@Entity<Article>('Articles', {
    allowApiRead: true,
    allowApiInsert: remult => remult.authenticated(),
    allowApiUpdate: (remult, article) => article.author.id == remult.user.id
})
export class Article {
    @Fields.string({ allowApiUpdate: false })
    slug = '';
    
    @Field(() => Profile, { allowApiUpdate: false })
    author!: Profile;

    @Fields.string()
    content = '';
}

Example App

CRM demo with a React + MUI front-end and Postgres database.

Contributing

Contributions are welcome. See CONTRIBUTING.

License

Remult is MIT Licensed.

About

A CRUD framework for full stack TypeScript

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 96.2%
  • HTML 2.5%
  • Other 1.3%