RESTful API Design met NodeJS & Restify

Wat je gaat creëren

De RESTful API bestaat uit twee hoofdconcepten: hulpbron, en Vertegenwoordiging. Resource kan elk object zijn dat is gekoppeld aan gegevens, of kan worden geïdentificeerd met een URI (meer dan één URI kan verwijzen naar dezelfde bron) en kan worden uitgevoerd met behulp van HTTP-methoden. Vertegenwoordiging is de manier waarop u de bron weergeeft. In deze tutorial behandelen we enkele theoretische informatie over RESTful API-ontwerp en implementeren we een voorbeeld van een API voor het bloggen met NodeJS.

hulpbron

Het kiezen van de juiste bronnen voor een RESTful API is een belangrijk onderdeel van het ontwerpen. Allereerst moet u uw bedrijfsdomein analyseren en vervolgens beslissen hoeveel en welke hulpbronnen worden gebruikt die relevant zijn voor uw bedrijfsbehoeften. Als u een blog-API ontwerpt, zult u deze waarschijnlijk gebruiken Artikel, Gebruiker, en Commentaar. Dat zijn de resourcenamen, en de bijbehorende gegevens zijn de bron zelf:

"title": "RESTful API ontwerpen", "content": "RESTful API-ontwerp is een zeer belangrijk geval in de wereld van softwareontwikkeling.", "author": "huseyinbabal", "tags": ["technology" , "nodejs", "node-restify"] "category": "NodeJS"

Hulpbronnen voor bronnen

U kunt doorgaan met een bewerking van de resource nadat u de benodigde resources hebt gekozen. Bewerking verwijst hier naar HTTP-methoden. Als u bijvoorbeeld een artikel wilt maken, kunt u het volgende verzoek doen:

POST / artikelen HTTP / 1.1 Host: localhost: 3000 Inhoudstype: application / json "title": "RESTful API Design with Restify", "slug": "restful-api-design-with-restify", "content" : "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", "Auteur": "huseyinbabal"

Op dezelfde manier kunt u een bestaand artikel bekijken door de volgende aanvraag in te dienen:

GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhoudstype: toepassing / json

Hoe zit het met het bijwerken van een bestaand artikel? Ik kan horen dat je zegt:

Ik kan een ander POST-verzoek doen naar / articles / update / 123456789012 met de payload.

Misschien beter, maar de URI wordt steeds complexer. Zoals we eerder al zeiden, kunnen bewerkingen verwijzen naar HTTP-methoden. Dit betekent, vermeld de bijwerken operatie in de HTTP-methode in plaats van dat in de URI. Bijvoorbeeld:

PUT / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhoudstype: application / json "title": "Bijgewerkt hoe RESTful API te ontwerpen", "inhoud": "Bijgewerkt RESTful API-ontwerp is een zeer belangrijk geval in de software development world. "," author ":" huseyinbabal "," tags ": [" technology "," nodejs "," restify "," one more tag "]" category ":" NodeJS "

Trouwens, in dit voorbeeld zie je tags en categorievelden. Dit hoeven geen verplichte velden te zijn. Je kunt ze leeg laten en ze in de toekomst instellen. 

Soms moet je een artikel verwijderen als het verouderd is. In dat geval kunt u een DELETE HTTP-aanvraag voor / Artikelen / 123456789012.

HTTP-methoden zijn standaardconcepten. Als u ze als bewerking gebruikt, heeft u eenvoudige URI's en een dergelijke eenvoudige API helpt u gelukkige consumenten te werven.

Wat als u een opmerking wilt toevoegen aan een artikel? U kunt het artikel selecteren en een nieuwe opmerking toevoegen aan het geselecteerde artikel. Door deze verklaring te gebruiken, kunt u de volgende aanvraag gebruiken:

POST / articles / 123456789012 / comments HTTP / 1.1 Host: localhost: 3000 Content-Type: application / json "text": "Wow! Dit is een goede tutorial", "auteur": "john doe"

De bovenstaande vorm van resource wordt genoemd als een sub-resource. Commentaar is een subresource van Artikel. De Commentaar bovenstaande payload wordt als een kind van. in de database ingevoegd Artikel. Soms verwijst een andere URI naar dezelfde bron. Als u bijvoorbeeld een specifieke opmerking wilt bekijken, kunt u een van de volgende gebruiken:

GET / articles / 123456789012 / Reacties / 123 HTTP / 1.1 Host: localhost: 3000 Inhoudstype: toepassing / json 

of:

GET / comments / 123456789012 HTTP / 1.1 Host: localhost: 3000 Inhoudstype: toepassing / json

Versioning

Over het algemeen veranderen API-functies regelmatig om nieuwe functies aan consumenten te bieden. In dat geval kunnen er tegelijkertijd twee versies van dezelfde API bestaan. Als u deze twee functies wilt scheiden, kunt u versiebeheer gebruiken. Er zijn twee vormen van versiebeheer

  1. Versie in URI: U kunt het versienummer in de URI opgeven. Bijvoorbeeld, /v1.1/articles/123456789012.
  2. Versie in kop: Geef het versienummer op in de kop en verander de URI nooit.Bijvoorbeeld:
GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Accepteer-versie: 1.0

In feite verandert de versie alleen de weergave van de bron, niet het concept van de bron. U hoeft dus de URI-structuur niet te wijzigen. In v1.1 is misschien een nieuw veld toegevoegd aan Artikel. Het geeft echter nog steeds een artikel terug. In de tweede optie is de URI nog steeds eenvoudig en hoeven consumenten hun URI niet te wijzigen in client-side implementaties. 

Het is belangrijk om een ​​strategie te ontwerpen voor situaties waarin de consument geen versienummer verstrekt. U kunt een foutmelding geven als de versie niet beschikbaar is, of u kunt een antwoord retourneren door de eerste versie te gebruiken. Als u de nieuwste stabiele versie als standaard gebruikt, kunnen consumenten veel fouten maken voor implementaties aan de clientzijde.

Vertegenwoordiging

Representatie is de manier waarop een API de resource weergeeft. Wanneer u een API-eindpunt belt, krijgt u een resource terug. Deze bron kan in elk formaat zijn zoals XML, JSON, etc. JSON heeft de voorkeur als u een nieuwe API ontwerpt. Als u echter een bestaande API bijwerkt die een XML-antwoord heeft geretourneerd, kunt u een andere versie van een JSON-antwoord opgeven. 

Dat is genoeg theoretische informatie over RESTful API-ontwerp. Laten we eens kijken naar het dagelijks gebruik door een Blogging-API te ontwerpen en implementeren met Restify.

Bloggen REST API

Ontwerp

Om een ​​RESTful API te ontwerpen, moeten we het bedrijfsdomein analyseren. Dan kunnen we onze middelen definiëren. In een blog-API moeten we:

  • Maken, bijwerken, verwijderen, bekijken Artikel
  • Maak een opmerking voor een specifiek Artikel, Bijwerken, Verwijderen, Bekijken, Commentaar
  • Maken, bijwerken, verwijderen, bekijken Gebruiker

In deze API ga ik niet in op het verifiëren van een gebruiker om een ​​artikel of opmerking te maken. Voor het authenticatiedeel kunt u verwijzen naar de Token-Based Authentication met de instructie AngularJS & NodeJS. 

Onze resourcenamen zijn klaar. Resource-bewerkingen zijn eenvoudig CRUD. U kunt de volgende tabel raadplegen voor een algemene showcase van API.

Naam van de bron HTTP-werkwoorden HTTP-methoden
Artikel maak een artikel
update artikel
verwijder artikel
bekijk artikel
POST / artikelen met Payload
PUT / artikelen / 123 met payload
VERWIJDEREN / artikelen / 123
GET / artikel / 123
Commentaar maak een reactie
update Coment
verwijder commentaar
bekijk de opmerking
POST / artikelen / 123 / opmerkingen met Payload
PUT / opmerkingen / 123 met payload
VERWIJDEREN / opmerkingen / 123
GET / opmerkingen / 123
Gebruiker maak Gebruiker aan
update Gebruiker
Verwijder gebruiker
bekijk Gebruiker
POST / gebruikers met Payload
PUT / gebruikers / 123 met payload
DELETE / gebruikers / 123
GET / gebruikers / 123

Project Setup

In dit project zullen we gebruiken NodeJS met Restify. De bronnen worden opgeslagen in de MongoDB database. Allereerst kunnen we resources definiëren als modellen in Restify.

Artikel

var mongoose = vereisen ("mangoest"); var Schema = mongoose.Schema; var ArticleSchema = new Schema (title: String, slug: String, content: String, author: type: String, ref: "User"); mongoose.model ('Article', ArticleSchema);

Commentaar

var mongoose = vereisen ("mangoest"); var Schema = mongoose.Schema; var CommentSchema = new Schema (text: String, article: type: String, ref: "Article", auteur: type: String, ref: "User"); mongoose.model ('Reactie', CommentSchema);

Gebruiker

Er vindt geen bewerking plaats voor de gebruikersresource. We gaan ervan uit dat we de huidige gebruiker al kennen die in staat is om op artikelen of opmerkingen te reageren.

Je kunt je afvragen waar deze mongoesmodule vandaan komt. Het is het meest populaire ORM-framework voor MongoDB geschreven als een NodeJS-module. Deze module is opgenomen in het project binnen een ander configuratiebestand. 

Nu kunnen we onze HTTP-werkwoorden definiëren voor de bovenstaande bronnen. Je kunt het volgende zien:

var restify = require ('restify'), fs = require ('fs') var controllers = , controllers_path = process.cwd () + '/ app / controllers' fs.readdirSync (controllers_path) .forEach (functie (bestand ) if (file.indexOf ('. js')! = -1) controllers [file.split ('.') [0]] = vereisen (controllers_path + '/' + file)) var server = restify.createServer (); server .use (restify.fullResponse ()) .use (restify.bodyParser ()) // Artikel Start server.post ("/ articles", controllers.article.createArticle) server.put ("/ articles /: id", controllers.article.updateArticle) server.del ("/ articles /: id", controllers.article.deleteArticle) server.get (path: "/ articles /: id", version: "1.0.0", controllers. article.viewArticle) server.get (path: "/ articles /: id", version: "2.0.0", controllers.article.viewArticle_v2) // Article End // Comment Start server.post ("/ comments" , controllers.comment.createComment) server.put ("/ comments /: id", controllers.comment.viewComment) server.del ("/ comments /: id", controllers.comment.deleteComment) server.get ("/ opmerkingen /: id ", controllers.comment.viewComment) // Commentaar End var port = process.en VAARTPORT || 3000; server.listen (poort, functie (err) if (err) console.error (err) else console.log ('App is klaar om:' + poort)) if (process.env.environment == 'production' ) process.on ('uncaughtException', function (err) console.error (JSON.parse (JSON.stringify (err, ['stack', 'message', 'inner'], 2))))

In dit codefragment worden in de eerste plaats de controllerbestanden met controllermethoden herhaald en worden alle controllers geïnitialiseerd om een ​​specifiek verzoek voor de URI uit te voeren. Hierna worden URI's voor specifieke bewerkingen gedefinieerd voor standaard CRUD-bewerkingen. Er is ook versiebeheer voor een van de bewerkingen op Artikel. 

Als u bijvoorbeeld de versie opgeeft als 2 in de header Accept-Version, viewArticle_v2 zal worden uitgevoerd. viewArticle en viewArticle_v2 beide doen hetzelfde werk, tonen de resource, maar ze tonen Article Resource in een ander formaat, zoals je kunt zien in de titel onderstaande veld. Ten slotte wordt de server gestart op een specifieke poort en worden enkele foutenrapportcontroles toegepast. We kunnen doorgaan met controlemechanismemethoden voor HTTP-bewerkingen op bronnen.

article.js

var mongoose = require ('mongoose'), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exportss.createArticle = function (req, res, next) var articleModel = new Article (req.body ); articleModel.save (functie (err, artikel) if (err) res.status (500); res.json (type: false, data: "Fout opgetreden:" + err) else res.json ( type: true, data: article)) exports.viewArticle = function (req, res, next) Article.findById (new ObjectId (req.params.id), function (err, article) if ( err) res.status (500); res.json (type: false, data: "Fout opgetreden:" + err) else if (article) res.json (type: true, data: article ) else res.json (type: false, data: "Article:" + req.params.id + "not found")) exports.viewArticle_v2 = function (req, res, next) Article.findById (nieuwe ObjectId (req.params.id), functie (err, artikel) if (err) res.status (500); res.json (type: false, data: "Fout opgetreden:" + 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 + "not found")) exports.updateArticle = function (req, res, next) var updatedArticleModel = nieuw artikel (req.body); Article.findByIdAndUpdate (nieuwe ObjectId (req.params.id), updatedArticleModel, function (err, article) if (err) res.status (500); res.json (type: false, data: "Fout opgetreden: "+ err) else if (article) res.json (type: true, data: article) else res.json (type: false, data:" Article: "+ req.params. id + "niet gevonden")) exports.deleteArticle = function (req, res, next) Article.findByIdAndRemove (new Object (req.params.id), function (err, article) if (err ) res.status (500); res.json (type: false, data: "Fout opgetreden:" + err) else res.json (type: true, data: "Article:" + req. params.id + "succesvol verwijderd")) 

Je kunt een uitleg vinden van de basis CRUD-bewerkingen aan de kant van de Mongoose hieronder:

  • Createarticle: Dit is een eenvoudig opslaan operatie op articleModel verzonden van de verzoekende instantie. Er kan een nieuw model worden gemaakt door de hoofdtekst van het verzoek door te geven als een constructor naar een model zoals var articleModel = nieuw artikel (req.body)
  • viewArticle: Om artikeldetails te bekijken, is een artikel-ID nodig in de URL-parameter. vind een met een ID-parameter is genoeg om artikeldetails te retourneren.
  • updateArticle: Artikelupdate is een eenvoudige zoekopdracht en enige gegevensmanipulatie op het geretourneerde artikel. Ten slotte moet het bijgewerkte model in de database worden opgeslagen door een opslaan commando.
  • deleteArticle: findByIdAndRemove is de beste manier om een ​​artikel te verwijderen door het artikel-ID op te geven.

De Mongoose-commando's die hierboven zijn genoemd, zijn gewoon statisch als methode door middel van het voorwerp van het Artikel dat ook een verwijzing is naar het Mongoose-schema.

comment.js

var mongoose = require ('mongoose'), Comment = mongoose.model ("Reactie"), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.viewComment = function (req, res)  Article.findOne ("comments._id": nieuwe ObjectId (req.params.id), "comments. $": 1, function (err, comment) if (err) res.status (500) ; res.json (type: false, data: "Fout opgetreden:" + err) else if (comment) res.json (type: true, data: new Comment (comment.comments [0]) ) else res.json (type: false, data: "Commentaar:" + req.params.id + "not found")) exports.updateComment = function (req, res, next) var updatedCommentModel = nieuwe opmerking (req.body); console.log (updatedCommentModel) Article.update ("comments._id": nieuwe ObjectId (req.params.id), "$ set": "comments. $. text": updatedCommentModel.text, "comments. $ .author ": updatedCommentModel.author, function (err) if (err) res.status (500); res.json (type: false, data:" Fout opgetreden: "+ err) else res.json (type: true, data: "Commentaar:" + req.params.id + "updated")) exports.deleteComment = function (req, res, next) Article.findOneAndUpdate ( "comments._id": nieuw ObjectId (req.params.id), "$ pull": "comments": "_id": nieuw ObjectId (req.params.id), function (err, artikel) if (err) res.status (500); res.json (type: false, data: "Fout opgetreden:" + err) else if (article) res.json (type: waar, data: artikel) else res.json (type: false, data: "Reactie:" + req.params.id + "not found"))

Wanneer u een verzoek indient bij een van de resource-URI's, wordt de gerelateerde functie die in de controller wordt vermeld, uitgevoerd. Elke functie binnen de controllerbestanden kan de req en res voorwerpen. De commentaar bron hier is een subresource van Artikel. Alle querybewerkingen worden uitgevoerd via het artikelmodel om een ​​subdocument te vinden en de benodigde update uit te voeren. Wanneer u echter een Commentaarbron probeert te bekijken, zult u er een zien, zelfs als er geen verzameling is in MongoDB.  

Andere ontwerpsuggesties

  • Selecteer gemakkelijk te begrijpen bronnen om consumenten gemakkelijk te kunnen gebruiken.
  • Laat zakelijke logica door consumenten implementeren. De artikelbron heeft bijvoorbeeld een veld met de naam naaktslak. Consumenten hoeven dit detail niet naar de REST-API te verzenden. Deze slug-strategie moet aan de REST API-kant worden beheerd om de koppeling tussen API en consumenten te verminderen. Consumenten hoeven alleen titeldetails te verzenden en u kunt de slug genereren op basis van uw bedrijfsbehoeften aan de REST API-kant.
  • Implementeer een autorisatielaag voor uw API-eindpunten. Niet-geautoriseerde consumenten hebben toegang tot beperkte gegevens die bij een andere gebruiker horen. In deze zelfstudie hebben we de gebruikersresource niet behandeld, maar u kunt verwijzen naar token gebaseerde verificatie met AngularJS & NodeJS voor meer informatie over API-authenticaties.
  • Gebruikers-URI in plaats van queryreeks. / Artikelen / 123  (Goed), / Artikelen? Id = 123 (Slecht).
  • Bewaar de staat niet; gebruik altijd directe invoer / uitvoer.
  • Gebruik een zelfstandig naamwoord voor uw bronnen. U kunt HTTP-methoden gebruiken om op bronnen te werken.

Als u ten slotte een RESTful API ontwerpt door deze fundamentele regels te volgen, heeft u altijd een flexibel, onderhoudbaar en gemakkelijk te begrijpen systeem.