Skip to content

Instantly share code, notes, and snippets.

@gr2m
Last active February 23, 2020 00:03
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gr2m/5463475 to your computer and use it in GitHub Desktop.
Save gr2m/5463475 to your computer and use it in GitHub Desktop.
Imagine saving and finding user data would work right in the browser, persistent and synchronised. How would the code look like? This is what I came up with. Forks & comments much appreciated! #nobackend #dreamcode
// add a new object
var type = 'note';
var attributes = {color: 'red'};
store.add(type, attributes)
.done(function (newObject) {});
.fail(function (error) {});
// update an existing object
var type = 'note';
var id = 'abc4567';
var update = {size: 2};
store.update(type, id, update)
.done(function (updatedObject) {});
// find one object
var type = 'note';
var id = 'abc4567';
store.find(type, id)
.done(function (object) {});
// Load all objects
store.findAll()
.done(function (objects) {});
// Load all objects from one type
var type = 'note';
store.findAll(type)
.done(function (objects) {});
// remove an existing object
var type = 'note';
var id = 'abc4567';
store.remove(type, id)
.done(function (removedObject) {});
// EVENTS
// listen to store events
store.on('add', function (newObject) {});
// new doc created
store.on('add', function (newObject) {});
// existing doc updated
store.on('update', function (updatedObject) {});
// doc removed
store.on('remove', function (removedObject) {});
// any of the events above
store.on('change', function (event, changedObject) {});
// all listeners can be filtered by type
store.on('add:note', function (newObject) {});
store.on('update:note', function (updatedObject) {});
store.on('remove:note', function (removedObject) {});
store.on('change:note', function (event, changedObject) {});
// ... and by type and id
store.on('update:note:uuid123', function (updatedObject) {});
store.on('remove:note:uuid123', function (removedObject) {});
store.on('change:note:uuid123', function (event, changedObject) {});
// ... react on changes coming from remote
store.on('add:note', function (newObject, options) {
if (options.remote) {
// do something wit
}
});
@nikolaymatrosov
Copy link

Deffinitly we would need some error handling. What if there is no object of note type with id = abc4567. I think find could be like this

// find one object
var type = 'note';
var id = 'abc4567';
hoodie.store.find(type, id)
  .done(function (object) {})
  .error(function (errorObject) {});

@gr2m
Copy link
Author

gr2m commented May 12, 2013

you're absolutely right, I just left them out because they've been so repetitive. I've added an error handler to the first example, as you suggested. Cheers!

@jpillora
Copy link

Hey Gregor, noBackend is a cool idea. Making clean APIs is definitely the direction I go when writing my backends. I'm working on something right now and I'm still deciding on the client side API's so this looks like a good source of creativity. My biggest issue for my noBackend solution is security, it needs to be bullet-proof and also provide powerful APIs for the front-end.

A suggestion for this datastore API would to add some kind of access control. In most real world situations, each user has restricted access to a subset of the total data. My backend has a basic concept of User Roles, where each user has an array of roles and I can restrict particular HTTP methods on particular URL endpoints to particular roles. It quickly gets complicated and a nice API is what I'm after. Here's a few slides on User Roles which seem to cover a few of the issues, though they are just one way to do access control, another solution may provide a cleaner API.

@gr2m
Copy link
Author

gr2m commented May 15, 2013

Thanks @jpillora, glad you like it!

I understand your security concerns: I get that feedback quite often, like here.

In general, the whole idea of noBackend is a simple, clean API, which abstracts away the backend and its complexity, including security constraints.

For example, instead of

if (currentUser.hasAccess('article:edit'))
{
  store.add('article', properties)
} else {
  showError("You are not allowed to add new articles")
}

it should be

store.add('article', properties).fail( showError )

makes sense?

@jpillora
Copy link

Yup, that does and a MONAD/chainable/fluent/promise-like API (however you want to refer to it) is a nice direction to go in. I guess I'm talking about something alittle different, I'm attempting to build a backend which has 2 responsibilities: database schema and database access. So the server is essentially a JSON object describing all database object schemas and also the rules that dictate what types of users can access these database objects, then server will hand these objects out over a RESTful API - and that's ALL it will do. This then passes the responsibility over to the frontend for the rest - which will hopefully result in an easy to use backend for front-end developers.

Say my new forum app has administrators, moderators and users. At least, we require administrators to have full access, moderators can delete/edit all posts and users can only edit/delete their own posts. This logic here needs to be on the server (this is the database access part) though when creating new users, the client must say which of 3 types above they will be (you can assume the access part prevents silly things like moderators creating admins).

This could be instead defined on the server so that when you say user.create the user type is contextually predefined depending on the current user. For me however, this is more logic that needs to be on the server, which I'm trying to move away from. So I guess our goals might diverge a bit here as I'm looking for a clean yet powerful API and you're looking for a small and simple API. The problem I see with small and simple as that if you want to abstract all of the backend away then some other developer will need to come and do the missing bits it for you. My goal is a 10% backend 90% frontend app, so one could write a complete app entirely(ish) in the front-end, whereas I think you're looking for the usual 50%, 50% approach, with backend guys just providing you with simpler APIs, which isn't really _no_Backend, thoughts ?

Anyway, that's kind of focused on the bigger picture, these APIs are good and I'll most likely write a companion front-end client to my server to achieve what you're after. I've already got a REST client which is close to what you've described: https://github.com/jpillora/jquery.rest

Currently my server is very beta, however I've got a few snippets in the README and you can get the gist of it: https://github.com/banchee/tranquil. At the moment I'm doing addResource and other adds though alternatively, it could be a command line tool which you point at a json file which is basically options and [resource, resource, ...].

@gr2m
Copy link
Author

gr2m commented May 17, 2013

I absolutely agree and very much encourage to suggest alternative APIs that diverge from my suggestions, for example in your case to server more complicated requirements, that's exactly the direction I'd love the discussion about Dreamcode to go. I just didn't find the right tool that really encourages this kind of discussion, gist is just a temporary solution at the moment.

I guess we'll have to create something on our own, unless someone can suggest a better way. But that's an off topic discussion, I've made an issue over here: noBackend/nobackend.org#10

@kerbymart
Copy link

How should we handle storing documents using this "store" such that not all fields are supposed to be returned back to the client. For example, I have an app that guest user can post appending password to it for later editing (without logging in), how can this scoped be handled. As such encrypted password should not be sent back to the client. What is the correct approach for this?

@danculley
Copy link

I would suggest replacing the objects returned from most of your methods (which have "done" and "fail" methods) with ES6-compliant promises, so as to avoid recreating APIs and to make it easier to interoperate with other APIs. That should be as simple as replacing "done" with "then" and "fail" with "catch", as well as supporting a two-argument version of then(onsuccess, onfailure).

@gr2m
Copy link
Author

gr2m commented Jan 19, 2015

@dancully The difference between .done & .fail to .then and .catch is: they have no side effects. Especially beginners can get really hard to understand weird behavior in their apps. The methods should still return standard promises, so they can be chained using .then and .catch, but that's out of scope here

@mk-pmb
Copy link

mk-pmb commented Jun 10, 2017

Whenever you think about users or moderators editing or deleting posts, also think of version history. People will contact your support about how someone stole their password and modified or removed their precious blog entry. Your lawyer will want to know whether for those five minutes yesterday evening, your blog might really have had the discount offer shown in that customer's screenshot, and how it may have come to appear there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment