remult 0.25.8 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -129
- package/esm/graphql.js +131 -59
- package/esm/index.js +1 -1
- package/esm/migrations/compare-migration-snapshots.js +61 -0
- package/esm/migrations/generate-migrations.js +68 -0
- package/esm/migrations/index.js +2 -0
- package/esm/migrations/migrate.js +90 -0
- package/esm/migrations/migration-types.js +36 -0
- package/esm/migrations/update-migrations-ts-file.js +115 -0
- package/esm/postgres/postgres-data-provider.js +24 -3
- package/esm/postgres/schema-builder.js +35 -26
- package/esm/remult-better-sqlite3.js +47 -0
- package/esm/remult-bun-sqlite.js +41 -0
- package/esm/remult-knex/index.js +180 -51
- package/esm/remult-mongo.js +2 -2
- package/esm/remult-sql-js.js +3 -78
- package/esm/remult-sqlite-core.js +104 -0
- package/esm/server/expressBridge.js +130 -29
- package/esm/server/initAsyncHooks.js +75 -1
- package/esm/src/CompoundIdField.js +8 -2
- package/esm/src/context.js +20 -24
- package/esm/src/data-api.js +71 -30
- package/esm/src/data-providers/rest-data-provider.js +17 -15
- package/esm/src/data-providers/sql-database.js +32 -7
- package/esm/src/data-providers/web-sql-data-provider.js +1 -9
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +37 -11
- package/esm/src/filter/filter-interfaces.js +229 -17
- package/esm/src/isOfType.js +9 -0
- package/esm/src/remult3/Fields.js +70 -18
- package/esm/src/remult3/RepositoryImplementation.js +47 -21
- package/esm/src/remult3/addValidator.js +15 -0
- package/esm/src/remult3/remult3.js +28 -57
- package/esm/src/server-action.js +4 -4
- package/esm/src/validators.js +6 -3
- package/esm/src/valueConverters.js +1 -1
- package/graphql.js +156 -71
- package/index.d.ts +4 -4
- package/index.js +2 -1
- package/migrations/compare-migration-snapshots.d.ts +27 -0
- package/migrations/compare-migration-snapshots.js +165 -0
- package/migrations/generate-migrations.d.ts +21 -0
- package/migrations/generate-migrations.js +104 -0
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +7 -0
- package/migrations/migrate.d.ts +27 -0
- package/migrations/migrate.js +175 -0
- package/migrations/migration-types.d.ts +48 -0
- package/migrations/migration-types.js +88 -0
- package/migrations/update-migrations-ts-file.d.ts +1 -0
- package/migrations/update-migrations-ts-file.js +141 -0
- package/package.json +21 -1
- package/postgres/postgres-data-provider.d.ts +8 -3
- package/postgres/postgres-data-provider.js +46 -3
- package/postgres/schema-builder.js +81 -48
- package/remult-better-sqlite3.d.ts +12 -0
- package/remult-better-sqlite3.js +64 -0
- package/remult-bun-sqlite.d.ts +11 -0
- package/remult-bun-sqlite.js +56 -0
- package/remult-knex/index.d.ts +12 -5
- package/remult-knex/index.js +257 -90
- package/remult-mongo.d.ts +2 -2
- package/remult-mongo.js +2 -2
- package/remult-sql-js.d.ts +2 -15
- package/remult-sql-js.js +11 -152
- package/remult-sqlite-core.d.ts +21 -0
- package/remult-sqlite-core.js +217 -0
- package/server/expressBridge.d.ts +30 -1
- package/server/expressBridge.js +140 -32
- package/server/initAsyncHooks.d.ts +16 -0
- package/server/initAsyncHooks.js +128 -2
- package/src/CompoundIdField.js +8 -2
- package/src/column-interfaces.d.ts +9 -3
- package/src/context.d.ts +8 -6
- package/src/context.js +27 -36
- package/src/data-api.d.ts +13 -9
- package/src/data-api.js +184 -135
- package/src/data-providers/rest-data-provider.js +26 -15
- package/src/data-providers/sql-database.d.ts +17 -5
- package/src/data-providers/sql-database.js +31 -7
- package/src/data-providers/web-sql-data-provider.d.ts +1 -2
- package/src/data-providers/web-sql-data-provider.js +4 -8
- package/src/entity.d.ts +64 -6
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +31 -7
- package/src/filter/filter-consumer-bridge-to-sql-request.js +46 -21
- package/src/filter/filter-interfaces.d.ts +106 -39
- package/src/filter/filter-interfaces.js +312 -32
- package/src/isOfType.d.ts +2 -0
- package/src/isOfType.js +14 -0
- package/src/remult3/Fields.d.ts +36 -0
- package/src/remult3/Fields.js +81 -19
- package/src/remult3/RepositoryImplementation.d.ts +7 -3
- package/src/remult3/RepositoryImplementation.js +106 -71
- package/src/remult3/addValidator.d.ts +2 -0
- package/src/remult3/addValidator.js +20 -0
- package/src/remult3/remult3.d.ts +6 -2
- package/src/remult3/remult3.js +28 -57
- package/src/server-action.d.ts +1 -1
- package/src/server-action.js +5 -4
- package/src/sql-command.d.ts +6 -1
- package/src/validators.d.ts +1 -0
- package/src/validators.js +10 -4
- package/src/valueConverters.js +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<a href="http://remult.dev/">
|
|
3
|
-
<img src="https://github.com/remult/remult/raw/master/docs/public/logo.png" width="140" height="140">
|
|
4
|
-
</a>
|
|
5
2
|
<h1>Remult</h1>
|
|
6
3
|
<p>Full-stack CRUD, simplified, with SSOT TypeScript entities</p>
|
|
7
4
|
<a href="https://circleci.com/gh/remult/remult/tree/master" rel="nofollow">
|
|
8
|
-
<img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield">
|
|
9
|
-
</a>
|
|
5
|
+
<img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield"></a>
|
|
10
6
|
<a href="https://codecov.io/gh/remult/remult" rel="nofollow">
|
|
11
|
-
<img src="https://codecov.io/gh/remult/remult/branch/master/graph/badge.svg?token=LYWQRUN3D1"
|
|
12
|
-
</a>
|
|
7
|
+
<img src="https://codecov.io/gh/remult/remult/branch/master/graph/badge.svg?token=LYWQRUN3D1"/></a>
|
|
13
8
|
<a href="https://raw.githubusercontent.com/remult/remult/master/LICENSE" rel="nofollow">
|
|
14
|
-
<img alt="GitHub license" src="https://img.shields.io/badge/license-MIT-blue.svg">
|
|
15
|
-
</a>
|
|
9
|
+
<img alt="GitHub license" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
16
10
|
<a href="https://www.npmjs.com/package/remult" rel="nofollow">
|
|
17
|
-
<img alt="npm version" src="https://badge.fury.io/js/remult.svg">
|
|
18
|
-
</a>
|
|
11
|
+
<img alt="npm version" src="https://badge.fury.io/js/remult.svg"></a>
|
|
19
12
|
<a href="https://www.npmjs.com/package/remult" rel="nofollow">
|
|
20
|
-
<img alt="npm downloads" src="https://img.shields.io/npm/dm/remult">
|
|
21
|
-
</a>
|
|
13
|
+
<img alt="npm downloads" src="https://img.shields.io/npm/dm/remult"></a>
|
|
22
14
|
<a href="https://discord.gg/GXHk7ZfuG5" rel="nofollow">
|
|
23
|
-
<img alt="Join Discord" src="https://badgen.net/discord/
|
|
24
|
-
</a>
|
|
15
|
+
<img alt="Join Discord" src="https://badgen.net/discord/members/GXHk7ZfuG5?icon=discord&label=Discord"/></a>
|
|
25
16
|
</div>
|
|
26
17
|
|
|
27
18
|
<br/>
|
|
@@ -35,67 +26,44 @@
|
|
|
35
26
|
<hr/>
|
|
36
27
|
<br/>
|
|
37
28
|
|
|
38
|
-
<a href="https://www.youtube.com/watch?v=rEoScmSVNUE" target="_blank">
|
|
39
|
-
<p align="center">
|
|
40
|
-
<img src="https://github.com/remult/remult/raw/master/static/images/video-thumbnail.jpg" alt="Video thumbnail" />
|
|
41
|
-
</p>
|
|
42
|
-
</a>
|
|
43
|
-
|
|
44
|
-
<a href="https://www.youtube.com/watch?v=rEoScmSVNUE" target="_blank">
|
|
45
|
-
<p align="center">Watch code demo on YouTube here (14 mins)</p>
|
|
46
|
-
</a>
|
|
47
|
-
|
|
48
29
|
## What is Remult?
|
|
49
30
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
backend ORM**.
|
|
31
|
+
Remult uses **TypeScript entities** as a single source of truth for: ✅ CRUD + Realtime API, ✅ frontend type-safe API client, and
|
|
32
|
+
✅ backend ORM.
|
|
53
33
|
|
|
54
|
-
- :zap: Zero-boilerplate CRUD API
|
|
55
|
-
- :ok_hand: Fullstack type-safety for API queries, mutations and RPC, without code generation
|
|
56
|
-
- :sparkles: Input validation, defined once
|
|
57
|
-
- :lock: Fine-grained code-based API authorization
|
|
34
|
+
- :zap: **Zero-boilerplate CRUD + Realtime API** with paging, sorting, and filtering
|
|
35
|
+
- :ok_hand: **Fullstack type-safety** for API queries, mutations and RPC, without code generation
|
|
36
|
+
- :sparkles: Input validation, **defined once**, runs both on the backend and on the frontend for best UX
|
|
37
|
+
- :lock: Fine-grained **code-based API authorization**
|
|
58
38
|
- :relieved: Incrementally adoptable
|
|
59
|
-
- :rocket: Production ready
|
|
60
|
-
- :mega: **NEW - Zero-boilerplate realtime live-queries**
|
|
61
|
-
|
|
62
|
-
### Status
|
|
63
39
|
|
|
64
|
-
Remult
|
|
65
|
-
However, we’re keeping the major version at zero so we can use community
|
|
66
|
-
feedback to finalize the v1 API.
|
|
40
|
+
**Remult supports all major databases**, including: PostgreSQL, MySQL, SQLite, MongoDB, MSSQL and Oracle.
|
|
67
41
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Full-stack web development is (still) too complicated. **Simple CRUD, a common
|
|
71
|
-
requirement of any business application, should be simple to build, maintain,
|
|
72
|
-
and extend** when the need arises.
|
|
73
|
-
|
|
74
|
-
Remult abstracts away repetitive, boilerplate, error-prone, and poorly designed
|
|
75
|
-
code on the one hand, and enables total flexibility and control on the other.
|
|
76
|
-
**Remult helps building fullstack apps using only TypeScript code you can easily
|
|
77
|
-
follow and safely refactor**, and fits nicely into any existing or new project
|
|
78
|
-
by being minimalistic and completely unopinionated regarding the developer’s
|
|
79
|
-
choice of other frameworks and tools.
|
|
80
|
-
|
|
81
|
-
Other frameworks tend to fall into either too much abstraction (no-code,
|
|
82
|
-
low-code, BaaS) or partial abstraction (MVC frameworks, GraphQL, ORMs, API
|
|
83
|
-
generators, code generators), and tend to be opinionated regarding the
|
|
84
|
-
development tool-chain, deployment environment, configuration/conventions or
|
|
85
|
-
DSL. Remult attempts to strike a better balance.
|
|
42
|
+
**Remult is frontend and backend framework agnostic** and comes with adapters for Express, Fastify, Next.js, Nuxt, SvelteKit, Nest, Koa, Hapi and Hono.
|
|
86
43
|
|
|
87
|
-
|
|
44
|
+
Remult promotes a **consistent query syntax for both frontend and Backend code**:
|
|
88
45
|
|
|
89
|
-
|
|
90
|
-
|
|
46
|
+
```ts
|
|
47
|
+
// Frontend - GET: /api/products?_limit=10&unitPrice.gt=5,_sort=name
|
|
48
|
+
// Backend - 'select name, unitPrice from products where unitPrice > 5 order by name limit 10'
|
|
49
|
+
await repo(Product).find({
|
|
50
|
+
limit: 10,
|
|
51
|
+
orderBy: {
|
|
52
|
+
name: 'asc',
|
|
53
|
+
},
|
|
54
|
+
where: {
|
|
55
|
+
unitPrice: { $gt: 5 },
|
|
56
|
+
},
|
|
57
|
+
})
|
|
91
58
|
|
|
92
|
-
|
|
93
|
-
|
|
59
|
+
// Frontend - PUT: '/api/products/product7' (body: { "unitPrice" : 7 })
|
|
60
|
+
// Backend - 'update products set unitPrice = 7 where id = product7'
|
|
61
|
+
await repo(Product).update('product7', { unitPrice: 7 })
|
|
94
62
|
```
|
|
95
63
|
|
|
96
64
|
## Usage
|
|
97
65
|
|
|
98
|
-
### Define
|
|
66
|
+
### Define schema in code
|
|
99
67
|
|
|
100
68
|
```ts
|
|
101
69
|
// shared/product.ts
|
|
@@ -106,6 +74,9 @@ import { Entity, Fields } from 'remult'
|
|
|
106
74
|
allowApiCrud: true,
|
|
107
75
|
})
|
|
108
76
|
export class Product {
|
|
77
|
+
@Fields.cuid()
|
|
78
|
+
id = ''
|
|
79
|
+
|
|
109
80
|
@Fields.string()
|
|
110
81
|
name = ''
|
|
111
82
|
|
|
@@ -114,65 +85,72 @@ export class Product {
|
|
|
114
85
|
}
|
|
115
86
|
```
|
|
116
87
|
|
|
117
|
-
###
|
|
88
|
+
### Add backend API with a [single line of code](https://remult.dev/docs/quickstart#server-side-initialization)
|
|
89
|
+
|
|
90
|
+
_Example:_
|
|
118
91
|
|
|
119
92
|
```ts
|
|
120
93
|
// backend/index.ts
|
|
121
94
|
|
|
122
95
|
import express from 'express'
|
|
123
|
-
import { remultExpress } from 'remult/remult-express'
|
|
96
|
+
import { remultExpress } from 'remult/remult-express' // adapters for: Fastify, Next.js, Nuxt, SvelteKit, Nest, more...
|
|
97
|
+
import { createPostgresDataProvider } from "remult/postgres" // supported: PostgreSQL, MySQL, SQLite, MongoDB, MSSQL and Oracle
|
|
124
98
|
import { Product } from '../shared/product'
|
|
125
99
|
|
|
126
|
-
const port = 3001
|
|
127
100
|
const app = express()
|
|
128
101
|
|
|
129
102
|
app.use(
|
|
130
103
|
remultExpress({
|
|
131
104
|
entities: [Product],
|
|
105
|
+
dataProvider: createPostgresDataProvider({
|
|
106
|
+
connectionString: 'postgres://user:password@host:5432/database"',
|
|
107
|
+
}),
|
|
132
108
|
}),
|
|
133
109
|
)
|
|
134
110
|
|
|
135
|
-
app.listen(
|
|
136
|
-
console.log(`Example API listening at http://localhost:${port}`)
|
|
137
|
-
})
|
|
111
|
+
app.listen()
|
|
138
112
|
```
|
|
139
113
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
```sh
|
|
143
|
-
> curl http://localhost:3001/api/products
|
|
144
|
-
|
|
145
|
-
[{"name":"Tofu","unitPrice":5}]
|
|
146
|
-
```
|
|
114
|
+
Remult adds route handlers for a fully functional REST API and realtime live-query endpoints, optionally including an [Open API](https://remult.dev/docs/adding-swagger) spec and a [GraphQL](https://remult.dev/docs/adding-graphql) endpoint
|
|
147
115
|
|
|
148
|
-
###
|
|
116
|
+
### Fetch data with type-safe frontend code
|
|
149
117
|
|
|
150
118
|
```ts
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
119
|
+
const [products, setProducts] = useState<Product[]>([])
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
repo(Product)
|
|
123
|
+
.find({
|
|
124
|
+
limit: 10,
|
|
125
|
+
orderBy: {
|
|
126
|
+
name: 'asc',
|
|
127
|
+
},
|
|
128
|
+
where: {
|
|
129
|
+
unitPrice: { $gt: 5 },
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
.then(setProducts)
|
|
133
|
+
}, [])
|
|
163
134
|
```
|
|
164
135
|
|
|
165
|
-
###
|
|
136
|
+
### :mega: Realtime Live Queries
|
|
166
137
|
|
|
167
138
|
```ts
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
return repo(Product)
|
|
141
|
+
.liveQuery({
|
|
142
|
+
limit: 10,
|
|
143
|
+
orderBy: {
|
|
144
|
+
name: 'asc',
|
|
145
|
+
},
|
|
146
|
+
where: {
|
|
147
|
+
unitPrice: { $gt: 5 },
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
.subscribe((info) => {
|
|
151
|
+
setProducts(info.applyChanges)
|
|
152
|
+
})
|
|
153
|
+
}, [])
|
|
176
154
|
```
|
|
177
155
|
|
|
178
156
|
### :ballot_box_with_check: Data validation and constraints - defined once
|
|
@@ -184,49 +162,52 @@ import { Entity, Fields, Validators } from 'remult'
|
|
|
184
162
|
allowApiCrud: true,
|
|
185
163
|
})
|
|
186
164
|
export class Product {
|
|
165
|
+
@Fields.cuid()
|
|
166
|
+
id = ''
|
|
167
|
+
|
|
187
168
|
@Fields.string({
|
|
188
169
|
validate: Validators.required,
|
|
189
170
|
})
|
|
190
171
|
name = ''
|
|
191
172
|
|
|
192
|
-
@Fields.
|
|
193
|
-
validate: (product) =>
|
|
194
|
-
if (product.description.trim().length < 50) {
|
|
195
|
-
throw 'too short'
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
})
|
|
199
|
-
description = ''
|
|
200
|
-
|
|
201
|
-
@Fields.number({
|
|
202
|
-
validate: (_, field) => {
|
|
203
|
-
if (field.value < 0) {
|
|
204
|
-
field.error = 'must not be less than 0' // or: throw "must not be less than 0";
|
|
205
|
-
}
|
|
206
|
-
},
|
|
173
|
+
@Fields.number<Product>({
|
|
174
|
+
validate: (product) => product.unitPrice > 0 || 'must be greater than 0',
|
|
207
175
|
})
|
|
208
176
|
unitPrice = 0
|
|
209
177
|
}
|
|
210
178
|
```
|
|
211
179
|
|
|
212
|
-
|
|
180
|
+
#### Enforced in frontend:
|
|
213
181
|
|
|
214
182
|
```ts
|
|
215
|
-
const product = productsRepo.create()
|
|
216
|
-
|
|
217
183
|
try {
|
|
218
|
-
await
|
|
184
|
+
await repo(Product).insert({ name: '', unitPrice: -1 })
|
|
219
185
|
} catch (e: any) {
|
|
220
|
-
console.error(e
|
|
186
|
+
console.error(e)
|
|
187
|
+
/* Detailed error object ->
|
|
188
|
+
{
|
|
189
|
+
"modelState": {
|
|
190
|
+
"name": "Should not be empty",
|
|
191
|
+
"unitPrice": "must be greater than 0"
|
|
192
|
+
},
|
|
193
|
+
"message": "Name: Should not be empty"
|
|
194
|
+
}
|
|
195
|
+
*/
|
|
221
196
|
}
|
|
222
197
|
```
|
|
223
198
|
|
|
224
|
-
|
|
199
|
+
#### Enforced in backend:
|
|
225
200
|
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{
|
|
201
|
+
```ts
|
|
202
|
+
// POST '/api/products' BODY: { "name":"", "unitPrice":-1 }
|
|
203
|
+
// Response: status 400, body:
|
|
204
|
+
{
|
|
205
|
+
"modelState": {
|
|
206
|
+
"name": "Should not be empty",
|
|
207
|
+
"unitPrice": "must be greater than 0"
|
|
208
|
+
},
|
|
209
|
+
"message": "Name: Should not be empty"
|
|
210
|
+
}
|
|
230
211
|
```
|
|
231
212
|
|
|
232
213
|
### :lock: Secure the API with fine-grained authorization
|
|
@@ -234,21 +215,60 @@ try {
|
|
|
234
215
|
```ts
|
|
235
216
|
@Entity<Article>('Articles', {
|
|
236
217
|
allowApiRead: true,
|
|
237
|
-
allowApiInsert:
|
|
238
|
-
allowApiUpdate: (article
|
|
218
|
+
allowApiInsert: Allow.authenticated,
|
|
219
|
+
allowApiUpdate: (article) => article.author == remult.user.id,
|
|
220
|
+
apiPrefilter: () => {
|
|
221
|
+
if (remult.isAllowed('admin')) return {}
|
|
222
|
+
return {
|
|
223
|
+
author: remult.user.id,
|
|
224
|
+
}
|
|
225
|
+
},
|
|
239
226
|
})
|
|
240
227
|
export class Article {
|
|
241
228
|
@Fields.string({ allowApiUpdate: false })
|
|
242
229
|
slug = ''
|
|
243
230
|
|
|
244
|
-
@
|
|
245
|
-
|
|
231
|
+
@Fields.string({ allowApiUpdate: false })
|
|
232
|
+
authorId = remult.user!.id
|
|
246
233
|
|
|
247
234
|
@Fields.string()
|
|
248
235
|
content = ''
|
|
249
236
|
}
|
|
250
237
|
```
|
|
251
238
|
|
|
239
|
+
### :rocket: Relations
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
await repo(Categories).find({
|
|
243
|
+
orderBy: {
|
|
244
|
+
name: 'asc ',
|
|
245
|
+
},
|
|
246
|
+
include: {
|
|
247
|
+
products: {
|
|
248
|
+
where: {
|
|
249
|
+
unitPrice: { $gt: 5 },
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Entity Definitions
|
|
256
|
+
export class Product {
|
|
257
|
+
//...
|
|
258
|
+
@Relations.toOne(Category)
|
|
259
|
+
category?: Category
|
|
260
|
+
}
|
|
261
|
+
export class Category {
|
|
262
|
+
//...
|
|
263
|
+
@Relations.toMany<Category, Product>(() => Product, `category`)
|
|
264
|
+
products?: Product[]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Automatic admin UI
|
|
269
|
+
|
|
270
|
+

|
|
271
|
+
|
|
252
272
|
## What about complex CRUD?
|
|
253
273
|
|
|
254
274
|
While simple CRUD shouldn’t require any backend coding, using Remult means
|
|
@@ -262,12 +282,20 @@ numerous ways:
|
|
|
262
282
|
[entity lifecycle hooks](https://remult.dev/docs/ref_entity.html#saving)
|
|
263
283
|
(before/after saving/deleting)
|
|
264
284
|
- Backend only updatable fields (e.g. “last updated at”)
|
|
265
|
-
-
|
|
266
|
-
[lazy/eager loading](https://remult.dev/docs/lazy-loading-of-related-entities.html)
|
|
285
|
+
- [Relations](https://remult.dev/docs/entity-relations.html)
|
|
267
286
|
- Roll-your-own type-safe endpoints with
|
|
268
287
|
[Backend Methods](https://remult.dev/docs/backendMethods.html)
|
|
269
288
|
- Roll-your-own low-level endpoints (Express, Fastify, koa, others…)
|
|
270
289
|
|
|
290
|
+
## Installation
|
|
291
|
+
|
|
292
|
+
The _remult_ package is one and the same for both the frontend bundle and the
|
|
293
|
+
backend. Install it once for a monolith project or per-repo in a monorepo.
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
npm i remult
|
|
297
|
+
```
|
|
298
|
+
|
|
271
299
|
## Tutorials
|
|
272
300
|
|
|
273
301
|
The best way to learn Remult is by following a tutorial of a simple Todo web app
|
|
@@ -279,6 +307,18 @@ with a Node.js Express backend.
|
|
|
279
307
|
- [Tutorial with Next.js](https://remult.dev/tutorials/react-next/)
|
|
280
308
|
- [Tutorial with Sveltekit](https://remult.dev/tutorials/sveltekit/)
|
|
281
309
|
|
|
310
|
+
## Demo
|
|
311
|
+
|
|
312
|
+
<a href="https://www.youtube.com/watch?v=rEoScmSVNUE" target="_blank">
|
|
313
|
+
<p align="center">
|
|
314
|
+
<img src="https://github.com/remult/remult/raw/master/static/images/video-thumbnail.jpg" alt="Video thumbnail" />
|
|
315
|
+
</p>
|
|
316
|
+
</a>
|
|
317
|
+
|
|
318
|
+
<a href="https://www.youtube.com/watch?v=rEoScmSVNUE" target="_blank">
|
|
319
|
+
<p align="center">Watch code demo on YouTube here (14 mins)</p>
|
|
320
|
+
</a>
|
|
321
|
+
|
|
282
322
|
## Documentation
|
|
283
323
|
|
|
284
324
|
The [documentation](https://remult.dev/docs) covers the main features of Remult.
|
|
@@ -293,6 +333,31 @@ However, it is still a work-in-progress.
|
|
|
293
333
|
- [CRM demo](https://github.com/remult/crm-demo) with a React +
|
|
294
334
|
[MUI](https://mui.com) front-end and Postgres database.
|
|
295
335
|
|
|
336
|
+
### Status
|
|
337
|
+
|
|
338
|
+
Remult is **production-ready** and, in fact, used in production apps since 2018.
|
|
339
|
+
However, we’re keeping the major version at zero so we can use community
|
|
340
|
+
feedback to finalize the v1 API.
|
|
341
|
+
|
|
342
|
+
## Motivation
|
|
343
|
+
|
|
344
|
+
Full-stack web development is (still) too complicated. **Simple CRUD, a common
|
|
345
|
+
requirement of any business application, should be simple to build, maintain,
|
|
346
|
+
and extend** when the need arises.
|
|
347
|
+
|
|
348
|
+
Remult abstracts away repetitive, boilerplate, error-prone, and poorly designed
|
|
349
|
+
code on the one hand, and enables total flexibility and control on the other.
|
|
350
|
+
**Remult helps building fullstack apps using only TypeScript code you can easily
|
|
351
|
+
follow and safely refactor**, and fits nicely into any existing or new project
|
|
352
|
+
by being minimalistic and completely unopinionated regarding the developer’s
|
|
353
|
+
choice of other frameworks and tools.
|
|
354
|
+
|
|
355
|
+
Other frameworks tend to fall into either too much abstraction (no-code,
|
|
356
|
+
low-code, BaaS) or partial abstraction (MVC frameworks, GraphQL, ORMs, API
|
|
357
|
+
generators, code generators), and tend to be opinionated regarding the
|
|
358
|
+
development tool-chain, deployment environment, configuration/conventions or
|
|
359
|
+
DSL. Remult attempts to strike a better balance.
|
|
360
|
+
|
|
296
361
|
## Contributing
|
|
297
362
|
|
|
298
363
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|