Créer ses propres routes d’API REST dans WordPress

Autre pièce charnière dans le développement d’un bon système back-end WordPress : la création d’une API sur-mesure pour permettre l’interaction avec d’autres services (front-end, outils tiers, sites connexes, etc.)

Heureusement, WordPress propose d’une part, par défaut, de nombreuses routes afin de lire et écrire des données dans un site. Mais surtout, il offre d’autre part aux développeurs un moyen simple et efficace d’enregistrer de nouvelles routes pour répondre à des besoins précis.


Améliorer ses compétences WordPress

Cet article fait partie d’une série de leçons présentant les différentes APIs et fonctions PHP disponibles dans WordPress. Découvrez l’article de présentation Comment devenir un bon développeur back-end WordPress ? pour en savoir plus et accéder aux autres leçons.

Qu’est-ce que l’API REST dans WordPress ?

Définition : une API est une interface de programmation d’application, et le mot REST signifie « REpresentational State Transfer » (transmission d’un état de représentation). C’est un ensemble de concepts permettant de modéliser et d’accéder aux données d’une application web via des objets et des collections interdépendants au format JSON.

L’API REST de WordPress fournit des points de communication REST (des adresses URLs) représentant les publications, les taxonomies et d’autres types de données présents dans le site WordPress.

Un service externe peut alors profiter de l’API pour envoyer et recevoir des données JSON à ces points de terminaison pour interroger, modifier et créer du contenu dans l’application WordPress. Lorsque vous demandez ou envoyez du contenu à l’API, la réponse sera également renvoyée au format JSON. 

Voici quelques exemples concrets de routes d’API REST développées pour des cas clients WordPress récents :

Bref, l’API REST de WordPress est suffisamment flexible pour répondre à tous les besoins d’une application web ou mobile.

Vous avez besoin d’une API pour votre application web ?

Je suis expert dans le développement de back-end WordPress sur-mesure.


Développer de nouvelles routes d’API REST

Code du plugin sur GitHub

Une extension d’exemple accompagne ce tutoriel et vous pouvez la télécharger sur GitHub et l’installez.

Dans le code du plugin que nous allons analyser pour cette leçon, 2 routes d’API maison ont été crées :

  1. l’une accepte les requêtes POST sur <site.fr>/wp-json/custom-api-route/create pour publier un article privé, en envoyant en body un JSON contenant un title et un content.
    • En cas de réponse positive (création), cette route répond avec l’ID du post créé et un token sécurisé assigné à ce post, utilisable par l’autre route pour supprimer ce post.
  2. l’autre accepte les requêtes DELETE sur <site.fr>/wp-json/custom-api-route/delete/<id> afin de supprimer un post précédemment créé. Elle attend un header X-WP-TOKEN contenant la valeur du token sécurisé du post correspondant afin de valider la requête.
    • En cas de réponse positive (suppression), cette route répond avec l’objet WP_Post fraîchement supprimé.

Prêts à écrire vos propres endpoints d’API REST ? Alors c’est parti !

Enregistrer une nouvelle route d’API

Si vous êtes un tant soit peu familier avec l’extension des logiques WordPress, alors vous remarquerez que la création d’une nouvelle URL d’API REST est relativement simple.

Il suffit d’appeler la fonction register_rest_route sur le hook rest_api_init et de fournir une fonction de rappel (callback) qui aura pour mission de traiter les requêtes sur le nouvel endpoint enregistré (voire les valider si vous ne souhaitez pas utiliser l’option validate_callback).

function register_routes() {
	register_rest_route(
		'custom-api-route',
		'/delete/(?P<post_id>\d+)',
		[
			'methods'             => 'DELETE',
			'callback'            => __NAMESPACE__ . '\\process_deletion',
			'permission_callback' => function( \WP_REST_Request $request ) {
				if ( get_post_meta( (int) $request->get_param( 'post_id' ), 'token', true ) !== $request->get_header( 'X-WP-TOKEN' ) ) {
					return new \WP_Error( 'post_deletion_denied', 'You cannot delete this post.' );
				}

				return true;
			},
			'args' => [
				'post_id' => [
					'validate_callback' => function ( $value, \WP_REST_Request $request, $key ) {
						if ( get_post_type( (int) $value ) !== 'post' ) {
							return new \WP_Error( 'post_id_invalid_value', 'Post not found.' );
						}

						return true;
					},
				]
			],
		]
	);
}
add_action( 'rest_api_init', __NAMESPACE__ . '\\register_routes' );

Regardons les paramètres de cette fonction d’un peu plus près :

  1. le premier est le namespace, c’est à dire le préfixe global qui précèdera vos routes placé après <site.com>/wp-json/,
  2. le deuxième est une chaîne de caractère indiquant la route à enregistrer. Elle est soit statique ou peut représenter un regex pour définir des arguments passés via l’URL, comme dans notre cas où un paramètre $post_id sera extrait de l’URL en enregistrant la route avec /delete/(?P<post_id>\d+),
  3. le troisième est un tableau d’options qui va nous permettre de définir la configuration de notre nouvelle route, avec entre autre :
    1. la méthode HTTP autorisée, via le paramètre $options['method'],
    2. la fonction de rappel à exécuter pour envoyer la réponse, via le paramètre $options['callback'],
    3. la fonction validant l’accès à cette route via le paramètre $options['permission_callback'],
    4. les spécificités des paramètres fournis par l’URL de la route via le paramètre $options['args'].

Seuls les trois premières options sont obligatoires.

Indiquer les paramètres autorisés, les valider et les nettoyer

Reprenons le paramètre $options['args'] (facultatif) utilisé pour enregistrer notre route de suppression de post :

'args' => [
	'post_id' => [
		'validate_callback' => function ( $value, \WP_REST_Request $request, $key ) {
			if ( ! is_numeric( $value ) ) {
				return new \WP_Error( 'post_id_invalid_format', 'Post ID should only contain digits.' );
			}

			if ( get_post_type( (int) $value ) !== 'post' ) {
				return new \WP_Error( 'post_id_invalid_value', 'Post not found.' );
			}

			return true;
		},
		'sanitize_callback' => function( $value ) {
			return absint( $value );
		},
	]
],

Il va nous permettre d’indiquer à WordPress les différentes logiques de validation que l’on attend sur tel ou tel argument de requête, ainsi que les éventuels nettoyages automatiques que l’on souhaite appliquer sur les valeurs entrantes.

Ce paramètre est en fait un tableau et chaque élément de ce tableau possède, en clé, l’argument à valider/nettoyer (post_id dans notre exemple) et en valeur un autre tableau où l’on peut fournir :

  1. un élément en clé validate_callback qui sera notre fonction de validation,
  2. un élément en clé sanitize_callback qui sera une fonction de nettoyage automatique.
Validation d’un paramètre entrant avec validate_callback

Dans l’exemple ci-dessus, on va donc profiter de ces 2 paramètres pour valider l’ID du post fourni en entrée (via le paramètre post_id passé dans l’URL) pour :

  1. s’assurer que c’est bien une valeur numérique,
  2. et vérifier que le post lié à cet ID est bien du type désiré.

Si une vérification n’est pas valide, on renvoie une erreur via un objet WP_Error() en indiquant un code d’erreur, un message lisible voire un tableau de données en 3è paramètre pour fournir une réponse encore plus précise aux consommateurs de l’API et modifier le statut HTTP de la réponse.

Un mot sur la modification du statut HTTP des réponses d’API en cas de réussite ou d’erreur

Si vous souhaitez renvoyer une erreur en réponse de la requête avec WP_Error, sachez que vous pouvez indiquer en 3è paramètre un tableau avec une clé status et une valeur représentant le statut (ou code) HTTP à renvoyer :

return new \WP_Error(
	'invalid_title',
	'Please provide a title with a length between 10 and 50 chars.',
	[ 'status' => 400 ]
);

Si la validation est bonne et que vous souhaitez par contre envoyer un statut HTTP autre que 200, la réponse à renvoyer doit être un objet WP_REST_Response instantié avec la valeur du statut en 2è paramètre :

return new \WP_REST_Response(
	[ 'success' => true ],
	201
);
Nettoyage automatique des valeurs reçues avec sanitize_callback
'args' => [
	'content' => [
		'sanitize_callback' => function( $value ) {
			return wp_kses_post( $value );
		},
	],
	'title' => [
		'sanitize_callback' => 'sanitize_text_field',
	],
],

Quant au nettoyage d’un paramètre, vous aurez remarqué que c’est assez simple : les fonctions que l’on définit vont recevoir la valeur en entrée, et vont pouvoir appliquer librement des fonctions pour nettoyer cette valeur et s’assurer qu’elle sera sécurisée lors du traitement final de la requête.

Les callbacks définis ici peuvent être :

  • des fonctions anonymes (cf le paramètre content ci-dessus),
  • ou simplement le nom d’une fonction de nettoyage qui recevra la valeur du paramètre transmis dans la requête (cf le paramètre title ci-dessus).

Référez-vous à ce guide du Theme Handbook pour découvrir toutes les fonctions de nettoyage disponibles dans WordPress.

Ajouter des règles d’autorisation

Il est obligatoire d’indiquer aussi une fonction de rappel pour définir les règles autorisant ou empêchant l’usage de notre route d’API. On utilise pour celà l’option permission_callback qui est une fonction recevant l’objet WP_REST_Request en paramètre à travers lequel on peut accéder au détail de la requête (body, headers, cookies, etc.).

Vous pouvez par exemple vérifier si l’utilisateur est connecté et possède un rôle spécifique. Si cette fonction anonyme renvoie un objet WP_Error, l’appel est court-circuité, une erreur est renvoyée en réponse et la logique de la route n’est pas exécutée. Si par contre la fonction renvoie true, la logique continue son chemin.

'permission_callback' => function( \WP_REST_Request $request ) {
	if ( get_post_meta( (int) $request->get_param( 'post_id' ), 'token', true ) !== $request->get_header( 'X-WP-TOKEN' ) ) {
		return new \WP_Error( 'post_deletion_denied', 'You cannot delete this post.', [ 'status' => 403 ] );
	}

	return true;
},

Dans notre cas, pour la route de suppression d’un post, on autorise la suppression uniquement si l’utilisateur fournit un header X-WP-TOKEN dont la valeur est équivalente au token de sécurité enregistré en métadonnée du post à supprimer.

Si ces 2 valeurs ne correspondent pas, la demande n’est pas valide et on retourne au consommateur de l’API un message erreur avec un statut 403 (denied).

Si elles correspondent, la demande de suppression est considérée comme authentique et valide : la suite de la logique sera exécutée.


Maintenant que l’on a correctement validé la requête et ses paramètres, voyons comment exécuter des logiques.

Exécuter des actions

Le fonction de callback définit dans register_rest_route() est le cœur de la logique d’une route d’API développée sur-mesure. C’est dans cette fonction qu’on va exécuter les actions propres à une route.

function process_deletion( \WP_REST_Request $request ) {
	// Suppression du post.
	$deleted_post = wp_delete_post( $request->get_param( 'post_id' ), true );

	return [
		'success' => ( $deleted_post instanceof \WP_Post ),
		'post'    => $deleted_post,
	];
}

Elle reçoit en paramètre un objet WP_REST_Request qui contient tous les détails de la requête en question. Dans l’exemple ci-dessus, on va supprimer le post et renvoyer en réponse un tableau associatif (qui deviendra un objet côté JSON). Donner libre cours à votre imagination et exécuter ce que bon vous semble dans cette fonction !

Un point sur la validation tardive

Notez que dans cette fonction de rappel, vous pouvez très bien effectuer encore d’autres logiques de vérification et retourner des WP_Error si vos règles de validation ne sont pas validées.
C’est une affaire de goût : vous pouvez décider de ne pas écrire beaucoup de paramètres dans register_rest_route() et centraliser la validation et le nettoyage dans votre fonction de callback, ou faire au contraire comme présenté ici.
J’ai tendance à préfèrer cette seconde technique car elle est plus lisible et maintenable : les fonctions d’enregistrement de mes routes intègrent toutes les « barrières » à passer en amont et les fonctions de rappel ne se concentrent que sur les actions à effectuer, en sachant que les validations sont OK et que les valeurs ont été préalablement nettoyées.

Accéder aux données de la requête

Cet objet $request possède plein de méthodes pratiques pour accéder aux données que le consommateur de notre API nous envoie. Entre autre :

Renvoyer une réponse appropriée aux consommateurs

Enfin, il est primordial de construire une réponse adéquate à renvoyer à l’utilisateur de l’API. Nous avons déjà vu comment renvoyer des erreurs proprement avec des WP_Error que WordPress transformera pour renvoyer un JSON au client.

En cas de réussite, on a deux possibilités. Soit on return un tableau associatif que l’on compose librement, qui sera servi en JSON à l’utilisateur.

Soit on return une instantiation de la classe WP_REST_Response construite :

  1. avec ce même tableau associatif en premier paramètre servant de corps de réponse,
  2. en deuxième paramètre, un integer pour définir le statut HTTP de la réponse,
  3. et en troisième paramètre, un tableau associatif permettant d’ajouter des headers spécifiques dans notre réponse.
return new \WP_REST_Response(
	[ 'success' => true ],
	201,
	[ 'WordPress-Custom-Header' => 1234 ]
);

Et voila ! Après tout ça, notre route d’API maison est maintenant prête à recevoir des appels, valider ou invalider leurs données, les nettoyer, exécuter des actions et renvoyer une réponse adaptée.

Pas mal, non ?

Aller plus loin avec l’API Rest WordPress

Comment développer une identification utilisateur avec token JWT pour une installation WordPress headless ?

Je me permets un petit aparté en bonus de ce tutoriel concernant l’intégration d’un système d’authentification utilisateur en AJAX par JWT dans WordPress. Quelques extensions existent pour ne pas réinventer la roue, comme JWT Auth – WordPress JSON Web Token Authentication.
C’est une telle extension qui m’a permis de gérer l’identification utilisateur en AJAX dans des projets d’applications mobiles comme freebulle ou TechniGrain.
Voici son fonctionnement pour créer une identification d’utilisateur WordPress en AJAX :
– on lance une requête POST sur la route /wp-json/jwt-auth/v1/token en indiquant le nom d’utilisateur et le mot de passe entrés par l’utilisateur dans un formulaire (dans l’appli mobile par exemple, ou un front React JS),
– si l’authentification est valide, cette route d’API renvoie un statut 200 accompagné d’un long token qu’il faudra stocker côté client,
– tous les futurs appels à des routes d’API nécessitant l’identification de l’utilisateur devront alors contenir un header Authorization: Bearer <token> : l’extension va intercepter ces appels, analyser ce header, le décoder et automatiquement « identifier » l’utilisateur durant la requête AJAX,
– le traitement de vos routes d’API pourra alors utiliser des fonctions comme get_current_user() ou get_current_user_id() comme si un utilisateur était identifié dans son navigateur grâce à un cookie.
Magique non ?

Comment créer un webhook dans WordPress ?

Les services tiers permettent souvent l’enregistrement de webhooks : plutôt que d’interroger leur API pour récupérer une donnée ou effectuer une action, c’est le service qui va appeler une route d’API de notre système WordPress afin de nous communiquer des informations à des moments précis.
Pour réussir la création d’un webhook sur-mesure dans WordPress, il suffit de suivre ce tutoriel et enregistrer votre propre route d’API REST à rendre disponible uniquement pour le service concerné, en pensant bien à intégrer une sécurité (token dans un header, clé d’API dans le body, IP whitelistée, etc.) pour n’accepter que les demandes émises par le service en question.
C’est ce que j’ai développé pour l’extension WP Mail Pro où une route d’API interne au site WordPress est déclarée par le plugin afin de permettre à SparkPost de lui communiquer des événements relatifs à la transmission d’e-mails (réception, ouverture, bounce, etc.).

Comment tester l’API REST de WordPress ?

Plusieurs possibilités :
– d’abord, installer l’extension JSON Formatter dans votre navigateur pour améliorer l’affichage des réponses JSON sur les pages web. Vous pouvez déjà tester des méthodes GET ainsi, comme par exemple lister les routes accessibles sur votre site sur l’URL <site.com>/wp-json/
– installer Postman ou Insomnia (mon préféré car plus léger) pour effectuer tout type de requêtes (GET, POST, DELETE, etc.) sur des URLs et ainsi tester les interactions avec une API spécifique. Ils vous permettent d’une part de sauvegarder et organiser vos requêtes, de définir les réglages de chacune (headers, body, méthode, etc.), de générer du code (cURL, fetch JS, etc) et bien plus encore. Un must-have pour tout bon développeur back-end souhaitant simplifier ses intégrations avec des APIs externes !


Si vous avez des questions spécifiques au développement d’API dans WordPress, ou souhaitez partager des commentaires, n’hésitez pas à utiliser le formulaire ci-dessous !


Vous avez aimé cet article ?

Partagez-le sur vos réseaux sociaux en guise de remerciement :)


Laisser un commentaire