CanJS — Build CRUD apps in fewer lines of code.
CanJS is a JavaScript framework for building CRUD apps in fewer lines of code.
Build CRUD apps in fewer lines of code
Learn how to build this CRUD appModel layer
Components shouldn’t be concerned with how data is fetched, updated, or cached.
CanJS provides the right abstractions for your model code to be cleanly separated from your UI code. Learn more…
Promises in templates
CanJS’s stache templating language can directly read the state and values from Promises.
No need to write any extra code to determine whether a Promise is pending, resolved, or rejected. Learn more…
{{# if(this.promise.isPending) }}
Loading…
{{/ if }}
{{# if(this.promise.isRejected) }}
Error: {{ this.promise.reason }}
{{/ if }}
{{# if(this.promise.isResolved) }}
Result: {{ this.promise.value }}
{{/ if }}
Real-time list updating
After data is created, updated, or destroyed, CanJS automatically updates your lists for you.
Filtering and sorting are preserved, so you don’t have to manually update your lists or fetch the same data again. Learn more…
Model layer
With a single line of code, CanJS creates a model that represents the objects returned by a backend API.
See how Todo
is created by passing a URL to realtimeRestModel().
The model layer is responsible for making GET, POST, PUT, and DELETE requests to your backend. With your component UI code using the model’s standard interface to make requests, if the backend API changes, you only have to configure the model and not change every component that uses that backend API.
By default, CanJS assumes your backend API is RESTful. If your backend API isn’t RESTful, that’s ok! CanJS has configuration options for you to control how it makes requests, parses data, and more.
import { realtimeRestModel } from "can";
const Todo = realtimeRestModel("/api/todos/{id}").Map;
// Get todos sorted by name
const todosPromise = Todo.getList({sort: "name"});
todosPromise.then(todos => {
// Your backend API might return something like:
// todos = [ {name: "a"}, {name: "b"}, {name: "c"} ]
});
Promises in templates
CanJS’s stache templating language is similar to Handlebars and Mustache.
Wherever you see {{ }}
in a template, CanJS evaluates the expression inside to either
print a value or perform some basic logic, like #if and
#for(of).
Stache is able to read the state and value of Promises. See isPending
, isRejected
,
and isResolved
being read on this.todosPromise
in the example code? Those return
true depending on the current state of the Promise. reason
is provided if the
Promise is rejected with an error, and value
contains the resolved value if
the promise succeeds.
These helpers make it much easier to include loading and error states in your app. We promise you’ll love writing your templates this way.
{{# if(this.todosPromise.isPending) }}
Loading todos…
{{/ if }}
{{# if(this.todosPromise.isRejected) }}
Error: {{ this.todosPromise.reason.message }}
{{/ if }}
{{# if(this.todosPromise.isResolved) }}
<ul>
{{# for(todo of this.todosPromise.value) }}
<li>
{{ todo.name }}
</li>
{{/ for }}
</ul>
{{/ if }}
Real-time list updating
Here you can see CanJS’s model layer in action. When Todo.getList({sort: "name"})
is called,
CanJS makes a GET request to /api/todos?sort=name
When the array of to-dos comes back, CanJS associates that array with the query {sort: "name"}
.
When new to-dos are created, they’re added to the list that’s returned automatically, and
in the right spot! You don’t have to write any code to make sure the new to-do gets inserted
into the right spot in the list.
CanJS does this for filtering as well. If you make a query with a filter (e.g. {filter: {complete: true}}
),
when items are added, edited, or deleted that match that filter, those lists will be updated automatically.
Save yourself time by not writing code that updates your app’s UI.
import { realtimeRestModel } from "can";
const Todo = realtimeRestModel("/api/todos/{id}").Map;
// Get completed todos
Todo.getList({sort: "name"}).then(todos => {
// Let’s assume the API came back with
// todos = [ {name: "a"}, {name: "c"} ]
// Create a new todo client-side
const newTodo = new Todo({name: "b"});
// The todos list is immediately updated with the
// new to-do in the right place, alphabetically:
// todos = [ {name: "a"}, {name: "b"}, {name: "d"} ]
});
Get started with just a few lines of code
Below is an entire app that shows off some of the best features of CanJS:
- One line of code to create a model from the data returned by a backend API (with realtimeRestModel).
isPending
,isRejected
,isResolved
, andvalue
helpers for directly reading the state of a Promise.- When you add a to-do, it automatically gets inserted into the list in the right position.
See the Pen CanJS 5 — Basic Todo App by Bitovi (@bitovi) on CodePen.
Who Uses CanJS?
Use DevTools to debug your app
Use the CanJS DevTools to edit your app’s state at runtime, visualize the dependency graphs between elements and state, and debug changes to observables.
Small bundle size
At 72 KB gzipped, CanJS provides all the tools you need at a small size.
Browser support
CanJS supports Internet Explorer 11, Chrome, Edge, Firefox, and Safari.