# Construire une ressource REST complète avec des actions

> Déclarez les actions query, read, insert, update, patch et remove sur la base NoSQL, avec validation, un 404 pour les enregistrements manquants et des routes qui associent chaque méthode HTTP.

Framework: total5
Product: Total.js 5 (total5@0.0.19)
Language: fr
Content-SHA256: 413b963b3691bddc06097f9f31d22309f7de83b3d7c94bc47e7cf8683686cb44
Snapshot: c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c
Sources: total5-installed@total5@0.0.19
Review: not-recorded

Une ressource REST se compose de six opérations : liste (`query`), `read`, `insert`, `update` (remplacer), `patch` (modifier quelques champs) et `remove`. Dans Total.js, chacune est une action avec sa propre validation, de sorte que la table des routes reste une simple correspondance entre URL et action. `params` valide l'identifiant de l'URL, `query` les filtres de la liste, `input` le corps. Dans une action, `$.params`, `$.query` et le corps validé (deuxième argument de `action`) sont déjà des valeurs propres.

Signalez un enregistrement manquant avec `$.invalid(404)` : il répond avec le code de statut et une erreur JSON, de la même façon pour chaque action. `$.success()` répond `{ "success": true }` et `$.callback(value)` répond avec une valeur, qui est en outre filtrée par le `output` de l'action. `UID()` crée l'identifiant d'un nouvel enregistrement.

**`patch` a besoin de `partial: true`.** Sans cela, un champ qui n'a pas été envoyé est tout de même rempli avec la valeur par défaut de son type (`''` pour un `String`), et `update()` écraserait la valeur enregistrée par ce texte vide. Avec `partial: true`, seuls les champs présents dans la requête atteignent l'action.

```js actions/products.js
NEWACTION('Products/query', {
	name: 'List products',
	query: 'page:Number, search:String',
	output: 'items:[id:UID, name:String, price:Number], page:Number',
	action: function($) {
		var page = $.query.page || 1;
		var builder = NOSQL('products').find().sort('name').take(10).skip((page - 1) * 10);
		if ($.query.search)
			builder.search('name', $.query.search);
		builder.callback((err, items) => $.callback({ items: items, page: page }));
	}
});

NEWACTION('Products/read', {
	name: 'Read a product',
	params: '*id:UID',
	output: 'id:UID, name:String, price:Number',
	action: function($) {
		NOSQL('products').read().where('id', $.params.id).callback(function(err, item) {
			if (item)
				$.callback(item);
			else
				$.invalid(404);
		});
	}
});

NEWACTION('Products/insert', {
	name: 'Create a product',
	input: '*name:String, *price:Number',
	action: function($, model) {
		model.id = UID();
		NOSQL('products').insert(model).callback(() => $.callback({ id: model.id }));
	}
});

NEWACTION('Products/update', {
	name: 'Replace a product',
	params: '*id:UID',
	input: '*name:String, *price:Number',
	action: function($, model) {
		NOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {
			if (count)
				$.success();
			else
				$.invalid(404);
		});
	}
});

NEWACTION('Products/patch', {
	name: 'Change some fields of a product',
	partial: true,
	params: '*id:UID',
	input: 'name:String, price:Number',
	action: function($, model) {
		NOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {
			if (count)
				$.success();
			else
				$.invalid(404);
		});
	}
});

NEWACTION('Products/remove', {
	name: 'Remove a product',
	params: '*id:UID',
	action: function($) {
		NOSQL('products').remove().where('id', $.params.id).callback(function(err, count) {
			if (count)
				$.success();
			else
				$.invalid(404);
		});
	}
});
```

```js controllers/products.js
exports.install = function() {
	ROUTE('GET    /products/            --> *Products/query');
	ROUTE('GET    /products/{id}/       --> *Products/read');
	ROUTE('POST   /products/            --> *Products/insert');
	ROUTE('PUT    /products/{id}/       --> *Products/update');
	ROUTE('PATCH  /products/{id}/       --> *Products/patch');
	ROUTE('DELETE /products/{id}/       --> *Products/remove');
};
```
