RESTful API Design mit NodeJS & Restify

Was Sie erstellen werden

Die RESTful-API besteht aus zwei Hauptkonzepten: Ressource, und Darstellung. Ressource kann ein beliebiges Objekt sein, das mit Daten verknüpft ist oder mit einem URI identifiziert wird (mehrere URIs können sich auf dieselbe Ressource beziehen) und kann mit HTTP-Methoden betrieben werden. Darstellung ist die Art, wie Sie die Ressource anzeigen. In diesem Tutorial werden einige theoretische Informationen zum RESTful-API-Design behandelt und ein Beispiel für eine Blogging-Anwendungs-API mithilfe von NodeJS implementiert.

Ressource

Die Auswahl der richtigen Ressourcen für eine RESTful-API ist ein wichtiger Abschnitt beim Entwerfen. Zunächst müssen Sie Ihre Geschäftsdomäne analysieren und dann entscheiden, wie viele und welche Ressourcen verwendet werden, die für Ihre geschäftlichen Anforderungen relevant sind. Wenn Sie eine Blogging-API entwerfen, werden Sie sie wahrscheinlich verwenden Artikel, Nutzer, und Kommentar. Dies sind die Ressourcennamen und die damit verbundenen Daten sind die Ressourcen selbst:

"title": "How to RESTful API entwerfen", "content": "RESTful API-Design ist ein sehr wichtiger Fall in der Welt der Softwareentwicklung.", "author": "huseyinbabal", "tags": ["technology" , "nodejs", "node-restify"] "category": "NodeJS"

Ressourcenverben

Sie können mit einer Ressourcenoperation fortfahren, nachdem Sie sich für die erforderlichen Ressourcen entschieden haben. Der Vorgang bezieht sich hier auf HTTP-Methoden. Um beispielsweise einen Artikel zu erstellen, können Sie die folgende Anfrage stellen:

POST / Artikel HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: application / json "title": "RESTful API-Design mit restify", "slug": "restful-api-design-with-restify", "content" : "Pellentesque habitant morbi tristique senectus et netus et malesuada macht Turpis egestas bekannt.", "Author": "huseyinbabal"

Auf dieselbe Weise können Sie einen vorhandenen Artikel anzeigen, indem Sie die folgende Anforderung ausgeben:

GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: application / json

Was ist mit dem Aktualisieren eines vorhandenen Artikels? Ich kann hören, dass Sie sagen:

Ich kann eine weitere POST-Anforderung an / articles / update / 123456789012 mit der Nutzlast stellen.

Vielleicht vorzuziehen, aber der URI wird komplexer. Wie bereits erwähnt, können Operationen auf HTTP-Methoden verweisen. Dies bedeutet, das angeben aktualisieren Operation in der HTTP-Methode, anstatt diese in den URI zu setzen. Zum Beispiel:

PUT / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: application / json "title": "Aktualisiertes Design für die RESTful-API", "content": "Das aktualisierte RESTful-API-Design ist ein sehr wichtiger Fall in der Software-Entwicklungswelt. "," Autor ":" huseyinbabal "," tags ": [" Technologie "," nodejs "," restify "," ein weiteres Tag "]" category ":" NodeJS "

In diesem Beispiel werden übrigens Tags und Kategorienfelder angezeigt. Das müssen keine Pflichtfelder sein. Sie können sie leer lassen und in Zukunft einstellen. 

Manchmal müssen Sie einen Artikel löschen, wenn er veraltet ist. In diesem Fall können Sie eine LÖSCHEN HTTP-Anfrage an / artikel / 123456789012.

HTTP-Methoden sind Standardkonzepte. Wenn Sie sie als Vorgang verwenden, verfügen Sie über einfache URIs, und diese einfache API hilft Ihnen, glückliche Konsumenten zu gewinnen.

Was ist, wenn Sie einen Kommentar zu einem Artikel einfügen möchten? Sie können den Artikel auswählen und dem ausgewählten Artikel einen neuen Kommentar hinzufügen. Mit dieser Anweisung können Sie die folgende Anfrage verwenden:

POST / articles / 123456789012 / comments HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: application / json "text": "Wow! Dies ist ein gutes Tutorial", "Autor": "john doe"

Die obige Form der Ressource wird als a bezeichnet Subressource. Kommentar ist eine unterressource von Artikel. Das Kommentar Die obige Nutzlast wird als untergeordnetes Element von in die Datenbank eingefügt Artikel. Manchmal bezieht sich ein anderer URI auf dieselbe Ressource. Um beispielsweise einen bestimmten Kommentar anzuzeigen, können Sie entweder Folgendes verwenden:

GET / articles / 123456789012 / comments / 123 HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: Anwendung / json 

oder:

GET / comments / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhaltstyp: Anwendung / json

Versionierung

Im Allgemeinen werden API-Funktionen häufig geändert, um Verbrauchern neue Funktionen zur Verfügung zu stellen. In diesem Fall können zwei Versionen derselben API gleichzeitig vorhanden sein. Um diese beiden Funktionen voneinander zu trennen, können Sie die Versionierung verwenden. Es gibt zwei Formen der Versionierung

  1. Version in URI: Sie können die Versionsnummer im URI angeben. Zum Beispiel, /v1.1/artikel/123456789012.
  2. Version im Header: Geben Sie die Versionsnummer im Header an und ändern Sie niemals den URI.Zum Beispiel:
GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Accept-Version: 1.0

Tatsächlich ändert die Version nur die Darstellung der Ressource, nicht das Konzept der Ressource. Sie müssen die URI-Struktur also nicht ändern. In Version 1.1 wurde dem Artikel möglicherweise ein neues Feld hinzugefügt. Es wird jedoch immer noch ein Artikel zurückgegeben. Bei der zweiten Option ist der URI immer noch einfach, und die Benutzer müssen ihren URI in clientseitigen Implementierungen nicht ändern. 

Es ist wichtig, eine Strategie für Situationen zu entwickeln, in denen der Verbraucher keine Versionsnummer angibt. Sie können einen Fehler auslösen, wenn keine Version bereitgestellt wird, oder Sie können eine Antwort zurückgeben, indem Sie die erste Version verwenden. Wenn Sie die neueste stabile Version als Standard verwenden, können Konsumenten viele Fehler für ihre clientseitigen Implementierungen erhalten.

Darstellung

Darstellung ist die Art und Weise, wie eine API die Ressource anzeigt. Wenn Sie einen API-Endpunkt aufrufen, wird eine Ressource zurückgegeben. Diese Ressource kann in einem beliebigen Format wie XML, JSON usw. vorliegen. JSON ist vorzuziehen, wenn Sie eine neue API entwerfen. Wenn Sie jedoch eine vorhandene API aktualisieren, mit der eine XML-Antwort zurückgegeben wurde, können Sie eine andere Version für eine JSON-Antwort angeben. 

Das sind genug theoretische Informationen über das API-Design von RESTful. Werfen wir einen Blick auf die reale Nutzung, indem wir eine Blogging-API mit Restify entwerfen und implementieren.

Blogging-REST-API

Design

Um eine RESTful-API zu entwerfen, müssen wir die Geschäftsdomäne analysieren. Dann können wir unsere Ressourcen definieren. In einer Blogging-API benötigen wir:

  • Erstellen, Aktualisieren, Löschen, Anzeigen Artikel
  • Erstellen Sie einen Kommentar für eine bestimmte Artikel, Aktualisieren, Löschen, Anzeigen, Kommentar
  • Erstellen, Aktualisieren, Löschen, Anzeigen Nutzer

In dieser API werde ich nicht beschreiben, wie ein Benutzer authentifiziert wird, um einen Artikel oder Kommentar zu erstellen. Für den Authentifizierungsteil können Sie sich auf das Tutorial Token-Based Authentication with AngularJS & NodeJS beziehen. 

Unsere Ressourcennamen sind fertig. Ressourcenoperationen sind einfach CRUD. In der folgenden Tabelle finden Sie einen allgemeinen Überblick über die API.

Ressourcenname HTTP-Verben HTTP-Methoden
Artikel Artikel erstellen
Artikel aktualisieren
Artikel löschen
Artikel ansehen
POST / Artikel mit Payload
PUT / Artikel / 123 mit Nutzlast
LÖSCHEN / Artikel / 123
GET / Artikel / 123
Kommentar Kommentar erstellen
coment aktualisieren
Kommentar löschen
Kommentar anzeigen
POST / Artikel / 123 / Kommentare mit Payload
PUT / Kommentare / 123 mit Payload
LÖSCHEN / Kommentare / 123
ERHALTEN / kommentieren / 123
Nutzer Benutzer erstellen
Benutzer aktualisieren
Benutzer löschen
Benutzer anzeigen
POST / Benutzer mit Payload
PUT / Benutzer / 123 mit Payload
LÖSCHEN / Benutzer / 123
GET / Benutzer / 123

Projektaufbau

In diesem Projekt werden wir verwenden NodeJS mit Wieder herstellen. Die Ressourcen werden im gespeichert MongoDB Datenbank. Zunächst können wir Ressourcen in Restify als Modelle definieren.

Artikel

var Mungo = Anfordern ("Mungo"); var Schema = mongoose.Schema; var ArticleSchema = neues Schema (title: String, Slug: String, Inhalt: String, Autor: Typ: String, ref: "User"); mongoose.model ('Article', ArticleSchema);

Kommentar

var Mungo = Anfordern ("Mungo"); var Schema = mongoose.Schema; var CommentSchema = neues Schema (Text: String, Artikel: Typ: String, Ref: "Artikel", Autor: Typ: String, Ref: "Benutzer"); mongoose.model ('Comment', CommentSchema);

Nutzer

Es gibt keine Operation für die Benutzerressource. Wir gehen davon aus, dass wir bereits den aktuellen Benutzer kennen, der Artikel oder Kommentare bearbeiten kann.

Sie können fragen, woher dieses Mungo-Modul kommt. Es ist das beliebteste ORM-Framework für MongoDB, das als NodeJS-Modul geschrieben wurde. Dieses Modul ist im Projekt in einer anderen Konfigurationsdatei enthalten. 

Jetzt können wir unsere HTTP-Verben für die oben genannten Ressourcen definieren. Sie können Folgendes sehen:

var restify = required ('restify'), fs = required ('fs') var controller = , controller_path = process.cwd () + '/ app / controller' fs.readdirSync (controller_path) .forEach (function ) if (file.indexOf ('. js')! = -1) controller [file.split ('.') [0]] = required (controller_path + '/' + file) var Server = restify.createServer (); server .use (restify.fullResponse ()) .use (restify.bodyParser ()) // Article Start server.post ("/ articles", controller.article.createArticle) server.put ("/ articles /: id", controller.article.updateArticle) server.del ("/ articles /: id", controller.article.deleteArticle) server.get (Pfad: "/ articles /: id", Version: "1.0.0", Controller. article.viewArticle) server.get (Pfad: "/ articles /: id", Version: "2.0.0", controller.article.viewArticle_v2) // Artikelende // Kommentar Start server.post ("/ comments") , controller.comment.createComment) server.put ("/ comments /: id", controller.comment.viewComment) server.del ("/ comments /: id", controller.comment.deleteComment) server.get ("/ comments) /: id ", controller.comment.viewComment) // Kommentar Ende var port = process.env.PORT || 3000; server.listen (port, function (err) if (err) console.error (err) else console.log ('App ist fertig unter:' + port)) if (process.env.environment == 'production' ) process.on ('uncaughtException', Funktion (err) console.error (JSON.parse (JSON.stringify (err, ['stack', 'message', 'inner']), 2))))

In diesem Code-Snippet werden zunächst die Controller-Dateien mit Controller-Methoden iteriert und alle Controller initialisiert, um eine bestimmte Anforderung an den URI auszuführen. Danach werden URIs für bestimmte Operationen für grundlegende CRUD-Operationen definiert. Es gibt auch eine Versionierung für eine der Vorgänge bei Article. 

Zum Beispiel, wenn Sie Version als angeben 2 im Accept-Version-Header, viewArticle_v2 wird durchgeführt. viewArticle und viewArticle_v2 Beide führen dieselbe Aufgabe aus und zeigen die Ressource an, aber sie zeigen die Artikelressource in einem anderen Format an, wie Sie im sehen können Titel Feld unten. Schließlich wird der Server an einem bestimmten Port gestartet und einige Fehlerberichtsüberprüfungen werden angewendet. Wir können mit Controller-Methoden für HTTP-Vorgänge auf Ressourcen fortfahren.

article.js

var mongoose = required ('mongoose'), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.createArticle = Funktion (req, res, next) var articleModel = neuer Artikel (req.body) ); articleModel.save (Funktion (err, article) if (err) res.status (500); res.json (Typ: false, Daten: "Fehler aufgetreten:" + err) else res.json ( Typ: true, data: article)) exports.viewArticle = function (req, res, next) Article.findById (neue ObjectId (req.params.id), function (err, article) if ( err) res.status (500); res.json (type: false, data: "Fehler ist aufgetreten:" + err) else if (article) res.json (type: true, data: article.) ) else res.json (Typ: false, Daten: "Artikel:" + req.params.id + "nicht gefunden") exports.viewArticle_v2 = function (req, res, next) Article.findById (neue ObjectId (req.params.id), Funktion (err, article) if (err) res.status (500); res.json (type: false, Daten: "Fehler aufgetreten:" + err) else if (article) article.title = article.title + "v2" res.json (type: true, data: article) else res.json (type: false, data.) : "Artikel:" + req.params.id + "nicht gefunden")) exports.updateArticle = function (req, res, next) var updatedArticleModel = neuer Artikel (erforderlicher Körper); Article.findByIdAndUpdate (neue ObjectId (req.params.id), aktualisierteArticleModel, Funktion (err, article) if (err) res.status (500); res.json (type: false, data: "Fehler: "+ err) else if (article) res.json (type: wahr, Daten: article) else res.json (type: false, data:" Artikel: "+ req.params). id + "nicht gefunden")) exports.deleteArticle = function (req, res, next) Article.findByIdAndRemove (neues Objekt (req.params.id), function (err, article) if (err ) res.status (500); res.json (Typ: false, Daten: "Fehler aufgetreten:" + err) else res.json (Typ: true, Daten: "Artikel:" + req.) params.id + "erfolgreich gelöscht")) 

Nachfolgend finden Sie eine Erläuterung der grundlegenden CRUD-Operationen auf der Mongoose-Seite:

  • createArticle: Das ist einfach sparen Betrieb an articleModel vom Anforderungshauptteil gesendet. Ein neues Modell kann erstellt werden, indem der Anforderungshauptteil als Konstruktor an ein Modell wie übergeben wird var articleModel = neuer Artikel (Anz. Körper)
  • viewArticle: Um die Artikeldetails anzuzeigen, ist im URL-Parameter eine Artikel-ID erforderlich. einen finden mit einem ID-Parameter reicht aus, um Artikeldetails zurückzugeben.
  • UpdateArtikel: Die Artikelaktualisierung ist eine einfache Suchabfrage und einige Datenmanipulationen am zurückgegebenen Artikel. Schließlich muss das aktualisierte Modell durch Ausgeben von a in der Datenbank gespeichert werden sparen Befehl.
  • deleteArtikel: findByIdAndRemove ist der beste Weg, um einen Artikel zu löschen, indem Sie die Artikel-ID angeben.

Die oben erwähnten Mongoose-Befehle sind einfach statisch wie die Methode "Article", die auch eine Referenz des Mongoose-Schemas ist.

comment.js

var mongoose = erfordern ('mongoose'), Kommentar = mongoose.model ("Kommentar"), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.viewComment = function (req, res)  Article.findOne ("comments._id"): neue ObjectId (req.params.id), "comments. $": 1, Funktion (err, comment) if (err) res.status (500) ; res.json (type: false, data: "Fehler aufgetreten:" + err) else if (comment) res.json (type: true, data: neuer Kommentar (comment.comments [0]) ) else res.json (Typ: false, Daten: "Kommentar:" + req.params.id + "nicht gefunden") exports.updateComment = function (req, res, next) var updatedCommentModel = neuer Kommentar (req.body); console.log (updatedCommentModel) Article.update ("comments._id": neue ObjectId (req.params.id), "$ set": "comments. $. text": updatedCommentModel.text, "Kommentare. $ .author ": updatedCommentModel.author, function (err) if (err) res.status (500); res.json (type: false, data:" Fehler aufgetreten: "+ err) else res.json (Typ: wahr, Daten: "Kommentar:" + req.params.id + "aktualisiert")) exports.deleteComment = function (req, res, next) Article.findOneAndUpdate ( "comments._id": neue ObjectId (req.params.id), "$ pull": "comments": "_id": neue ObjectId (req.params.id), Funktion (err, article) if (err) res.status (500); res.json (Typ: falsch, Daten: "Fehler aufgetreten:" + err) else if (article) res.json (Typ: true, data: article) else res.json (type: false, data: "Kommentar:" + req.params.id + "nicht gefunden")

Wenn Sie eine Anforderung an einen der Ressourcen-URIs stellen, wird die im Controller angegebene zugehörige Funktion ausgeführt. Jede Funktion in den Controller-Dateien kann das verwenden req und res Objekte. Das Kommentar Ressource hier ist eine Subressource von Artikel. Alle Abfragevorgänge werden über das Artikelmodell ausgeführt, um ein Unterdokument zu finden und die erforderlichen Aktualisierungen vorzunehmen. Wenn Sie jedoch versuchen, eine Comment-Ressource anzuzeigen, wird eine angezeigt, selbst wenn in MongoDB keine Sammlung vorhanden ist.  

Andere Designvorschläge

  • Wählen Sie leicht verständliche Ressourcen aus, um den Verbrauchern eine einfache Verwendung zu ermöglichen.
  • Lassen Sie die Geschäftslogik von den Verbrauchern implementieren. Beispielsweise hat die Artikelressource ein Feld namens Schnecke. Verbraucher müssen dieses Detail nicht an die REST-API senden. Diese Slug-Strategie sollte auf der REST-API-Seite verwaltet werden, um die Kopplung zwischen API und Verbrauchern zu reduzieren. Verbraucher müssen nur die Titeldetails senden, und Sie können den Slug entsprechend Ihren Geschäftsanforderungen auf der REST-API-Seite generieren.
  • Implementieren Sie eine Berechtigungsschicht für Ihre API-Endpunkte. Nicht autorisierte Verbraucher können auf eingeschränkte Daten zugreifen, die einem anderen Benutzer gehören. In diesem Lernprogramm haben wir die Benutzerressource nicht behandelt. Weitere Informationen zu API-Authentifizierungen finden Sie unter Token-basierte Authentifizierung mit AngularJS & NodeJS.
  • Benutzer-URI statt Abfragezeichenfolge. / artikel / 123  (Gut), / articles? id = 123 (Schlecht).
  • Behalte den Zustand nicht bei; Verwenden Sie immer die sofortige Eingabe / Ausgabe.
  • Verwenden Sie ein Substantiv für Ihre Ressourcen. Sie können HTTP-Methoden verwenden, um Ressourcen zu bearbeiten.

Wenn Sie eine RESTful-API mit diesen grundlegenden Regeln entwerfen, haben Sie immer ein flexibles, wartbares und leicht verständliches System.