RESTful API Design med NodeJS & Restify

Vad du ska skapa

RESTful API består av två huvud begrepp: Resurs, och Representation. Resurs kan vara något objekt som är associerat med data eller identifieras med en URI (mer än en URI kan referera till samma resurs) och kan användas med HTTP-metoder. Representation är hur du visar resursen. I den här handledningen kommer vi att täcka några teoretiska uppgifter om RESTful API-design och implementera ett exempel på API för blogging med NodeJS.

Resurs

Att välja rätt resurser för ett RESTful API är en viktig del av designen. Först och främst måste du analysera din företagsdomän och bestämma sedan hur många och vilka resurser som ska användas som är relevanta för ditt företags behov. Om du utformar ett bloggprogram, använder du förmodligen Artikel, Användare, och Kommentar. Det är resursnamnen, och de uppgifter som är förknippade med det är själva resursen:

"title": "Hur man utformar RESTful API", "content": "RESTful API-design är ett mycket viktigt fall i programvaruutvecklingsvärlden.", "Författare": "huseyinbabal", "tags": ["technology" , "nodejs", "node-restify"] "category": "NodeJS"

Resurs Verbs

Du kan fortsätta med en resursoperation när du har bestämt dig för de nödvändiga resurserna. Drift här hänvisar till HTTP-metoder. För att till exempel skapa en artikel kan du göra följande förfrågan:

POST / artiklar HTTP / 1.1 Host: localhost: 3000 Innehållstyp: application / json "title": "RESTful API Design with Restify", "slug": "restful-api-design-with-restify" : "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", "Författare": "huseyinbabal"

På samma sätt kan du se en befintlig artikel genom att utfärda följande förfrågan:

GET / articles / 123456789012 HTTP / 1.1 Värd: localhost: 3000 Innehållstyp: application / json

Vad sägs om att uppdatera en befintlig artikel? Jag kan höra att du säger:

Jag kan göra en annan POST-förfrågan till / artiklar / uppdatering / 123456789012 med nyttolasten.

Kanske föredraget, men URI blir alltmer komplex. Som vi tidigare sagt kan operationer hänvisa till HTTP-metoder. Detta betyder, ange uppdatering operation i HTTP-metoden istället för att sätta det i URI. Till exempel:

PUT / articles / 123456789012 HTTP / 1.1 Värd: localhost: 3000 Innehållstyp: application / json "title": "Uppdaterad hur man utformar RESTful API", "content": "Uppdaterad RESTful API-design är ett mycket viktigt fall i mjukvaruutvecklingsvärld. "," författare ":" huseyinbabal "," tags ": [" technology "," nodejs "," restify "," one more tag "]" category ":" NodeJS "

Förresten, i det här exemplet ser du taggar och kategorifält. De behöver inte vara obligatoriska fält. Du kan lämna dem tomma och ställa in dem i framtiden. 

Ibland måste du radera en artikel när den är föråldrad. I så fall kan du använda en RADERA HTTP-begäran till / artiklar / 123456789012.

HTTP-metoder är standardkoncept. Om du använder dem som en operation har du enkla URI-filer, och den här typen av enkelt API hjälper dig att få glada konsumenter.

Vad händer om du vill lägga in en kommentar till en artikel? Du kan välja artikeln och lägga till en ny kommentar till den valda artikeln. Genom att använda detta uttalande kan du använda följande förfrågan:

POST / articles / 123456789012 / comments HTTP / 1.1 Värd: localhost: 3000 Innehållstyp: application / json "text": "Wow! Detta är en bra handledning", "författare": "John Doe"

Ovanstående resursform kallas som en sub-resurs. Kommentar är en delresurs av Artikel. De Kommentar nyttolast ovan kommer att införas i databasen som ett barn av Artikel. Ibland avser en annan URI samma resurs. Om du till exempel vill visa en specifik kommentar kan du använda antingen:

GET / articles / 123456789012 / comments / 123 HTTP / 1.1 Värd: localhost: 3000 Innehållstyp: application / json 

eller:

GET / comments / 123456789012 HTTP / 1.1 Värd: localhost: 3000 Innehållstyp: application / json

versionshantering

I allmänhet ändras API-funktionerna ofta för att ge nya funktioner till konsumenterna. I det fallet kan två versioner av samma API existera samtidigt. För att skilja dessa två funktioner, kan du använda versioning. Det finns två former av versionering

  1. Version i URI: Du kan ange versionsnumret i URI. Till exempel, /v1.1/articles/123456789012.
  2. Version i rubrik: Ange versionsnumret i rubriken och ändra aldrig URI.Till exempel:
GET / articles / 123456789012 HTTP / 1.1 Värd: localhost: 3000 Accept-Version: 1.0

I själva verket ändras versionen endast av resursens representation, inte resursens begrepp. Så, du behöver inte ändra URI-strukturen. I v1.1, kanske ett nytt fält lagts till i artikeln. Det returnerar dock fortfarande en artikel. I det andra alternativet är URI fortfarande enkelt och konsumenter behöver inte ändra sina URI på klientsidan implementeringar. 

Det är viktigt att utforma en strategi för situationer där konsumenten inte tillhandahåller ett versionsnummer. Du kan hämta ett fel när version inte finns, eller du kan returnera ett svar med den första versionen. Om du använder den senaste stabila versionen som standard kan konsumenterna få många fel för sina implementeringar på klienten.

Representation

Representation är hur ett API visar resursen. När du ringer till en API-slutpunkt kommer du att få tillbaka en resurs. Denna resurs kan vara i något format som XML, JSON, etc. JSON är att föredra om du utformar ett nytt API. Men om du uppdaterar ett befintligt API som används för att returnera ett XML-svar, kan du tillhandahålla en annan version för ett JSON-svar. 

Det finns tillräckligt med teoretisk information om RESTful API-design. Låt oss ta en titt på verkligheten genom att utforma och implementera ett Blogging API med hjälp av Restify.

Blogging REST API

Design

För att kunna designa ett RESTful API måste vi analysera affärsdomen. Då kan vi definiera våra resurser. I ett Blogging API behöver vi:

  • Skapa, uppdatera, ta bort, visa Artikel
  • Skapa en kommentar för en specifik Artikel, Uppdatera, Radera, Visa, Kommentar
  • Skapa, uppdatera, ta bort, visa Användare

I detta API täcker jag inte hur jag autentiserar en användare för att skapa en artikel eller kommentar. För autentiseringsdelen kan du referera till Tokenbaserad autentisering med AngularJS & NodeJS handledning. 

Våra resursnamn är redo. Resursverksamheten är helt enkelt CRUD. Du kan referera till följande tabell för en allmän presentation av API.

Resursnamn HTTP Verbs HTTP-metoder
Artikel skapa artikel
uppdatera artikeln
ta bort artikel
visa artikel
POST / artiklar med nyttolast
PUT / artiklar / 123 med nyttolast
DELETE / articles / 123
GET / artikel / 123
Kommentar skapa kommentar
uppdatera kommentaren
ta bort kommentar
visa kommentar
POST / artiklar / 123 / kommentarer med nyttolast
PUT / kommentarer / 123 med nyttolast
DELETE / comments / 123
GET / comments / 123
Användare skapa användare
uppdatera användaren
Ta bort Användare
visa användare
POST / användare med nyttolast
PUT / användare / 123 med nyttolast
DELETE / users / 123
GET / users / 123

Projektinställningar

I det här projektet kommer vi att använda NodeJS med Restify. Resurserna sparas i MongoDB databas. Först av allt kan vi definiera resurser som modeller i Restify.

Artikel

var mongoose = kräver ("mongoose"); var Schema = mongoose. Schema; var ArticleSchema = nytt schema (title: String, slug: String, innehåll: String, författare: typ: String, ref: "User"); mongoose.model ("Article", ArticleSchema);

Kommentar

var mongoose = kräver ("mongoose"); var Schema = mongoose. Schema; var CommentSchema = nytt schema (text: String, artikel: typ: String, ref: "Article", författare: typ: String, ref: "User")); mongoose.model ('Comment', CommentSchema);

Användare

Det finns ingen operation för användarresursen. Vi antar att vi redan vet den nuvarande användaren som kommer att kunna arbeta på artiklar eller kommentarer.

Du kan fråga var denna mongoosmodul kommer ifrån. Det är den mest populära ORM-ramen för MongoDB skrivet som en NodeJS-modul. Den här modulen ingår i projektet inom en annan config-fil. 

Nu kan vi definiera våra HTTP-verb för ovanstående resurser. Du kan se följande:

var restify = require ('restify'), fs = kräver ('fs') var controllers = , controllers_path = process.cwd () + '/ app / controllers'fs.readdirSync (controllers_path) .forEach (funktion ) if (file.indexOf ('. js')! = -1) controllers [file.split ('.') [0]] = kräver (controllers_path + '/' + fil)) 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) // Artikel Slut // Kommentar Start server.post ("/ comments" , controllers.comment.createComment) server.put ("/ kommentarer /: id", controllers.comment.viewComment) server.del ("/ kommentarer /: id", controllers.comment.deleteComment) server.get ("/ kommentarer /: id ", controllers.comment.viewComment) // Kommentar Slut var port = process.env.PORT || 3000; server.listen (port, funktion (err) om (err) console.error (err) else console.log ('App är klar till:' + port)) om (process.env.environment == 'production' ) process.on ('uncaughtException', funktion (err) console.error (JSON.parse (JSON.stringify (err, ['stack', 'message', 'inner'], 2)))))

I det här kodbiten, först och främst kontrollerfilerna som innehåller regleringsmetoder itereras och alla styrenheter initieras för att utföra en specifik begäran till URI. Därefter definieras URI för specifika operationer för grundläggande CRUD-operationer. Det finns också versioning för en av transaktionerna på artikeln. 

Till exempel, om du anger version som 2 i Accept-Version header, viewArticle_v2 kommer att utföras. viewArticle och viewArticle_v2 båda gör samma jobb och visar resursen, men de visar artikelresurs i ett annat format, som du kan se i titel fält nedan. Slutligen startas servern på en viss port, och vissa felrapporteringskontroller tillämpas. Vi kan fortsätta med kontrollmetoder för HTTP-operationer på resurser.

article.js

var mongoose = kräver ('mongoose'), Artikel = mongoose.model ("Artikel"), ObjectId = mongoose.Types.ObjectId exports.createArticle = funktion (req, res, nästa) var articleModel = ny artikel (req.body ); articleModel.save (funktion (fel, artikel) om (err) res.status (500); res.json (typ: false, data: "Fel uppstod:" + err) else res.json type: true, data: article)) exports.viewArticle = funktion (req, res, nästa) Article.findById (new ObjectId (req.params.id) err) res.status (500); res.json (typ: false, data: "Fel uppstod:" + err) annat om (artikel) res.json (typ: true, data: article  annat res.json (typ: falskt, data: "Artikel:" + req.params.id + "not found")) exports.viewArticle_v2 = funktion (req, res, nästa) Article.findById (new ObjectId (req.params.id), funktion (err, artikel) om (err) res.status (500); res.json (typ: false, data: "Fel uppstod:" + err) annat om (artikel) article.title = article.title + "v2" res.json (typ: true, data: article) annat res.json : "Artikel:" + req.params.id + "not found")) exports.updateArticle = funktion (req, res, nästa) var updatedArticleModel = ny artikel (req.body); Article.findByIdAndUpdate (new ObjectId (req.params.id), updatedArticleModel, funktion (err, artikel) om (err) res.status (500); res.json (typ: false, data: "Fel uppstod: "+ err) annat om (artikel) res.json (typ: true, data: article) else res.json (typ: false, data:" Article: "+ req.params. id = "inte hittat") exports.deleteArticle = funktion (req, res, nästa) Article.findByIdAndRemove (nytt objekt (req.params.id) ) res.json (typ: false, data: "Fel uppstod:" + err) annat res.json (typ: true, data: "Artikel:" + req. params.id + "raderas framgångsrikt")) 

Du kan hitta en förklaring av grundläggande CRUD-operationer på Mongoose-sidan nedan:

  • Createarticle: Det här är enkelt spara operation på articleModel skickat från begäran organ. En ny modell kan skapas genom att överföra begäran kroppen som en konstruktör till en modell som var articleModel = ny artikel (req.body)
  • viewArticle: För att kunna se artikeldetaljer behövs ett artikel-ID i URL-parametern. hitta en med en ID-parameter är tillräckligt för att returnera artikeldetaljer.
  • updateArticle: Artikeluppdatering är en enkel sökfråga och viss dataprofilering på den returnerade artikeln. Slutligen måste den uppdaterade modellen sparas i databasen genom att utfärda en spara kommando.
  • deleteArticle: findByIdAndRemove är det bästa sättet att ta bort en artikel genom att tillhandahålla artikel-id.

De ovan nämnda mongooskommandon är helt enkelt statiska som metod genom artikelobjekt som också är en hänvisning till mongooschemat.

comment.js

var mongoose = kräver ('mongoose'), Kommentar = mongoose.model ("Kommentar"), Artikel = mongoose.model ("Artikel"), ObjectId = mongoose.Types.ObjectId exports.viewComment = funktion (req, res)  Article.findOne ("comments._id": new ObjectId (req.params.id), "kommentarer. $": 1, funktion (fel, kommentar) om (err) res.status (500) ; res.json (typ: false, data: "Fel uppstod:" + err) annat om (kommentar) res.json (typ: true, data: new Kommentar (comment.comments [0])  res.json (typ: falskt, data: "Kommentar:" + req.params.id + "not found")) exports.updateComment = funktion (req, res, nästa) var updatedCommentModel = ny kommentar (req.body); console.log (updatedCommentModel) Article.update ("comments._id": nytt ObjectId (req.params.id), "$ set": "kommentarer. $. text": updatedCommentModel.text, "kommentarer. $ .author ": updatedCommentModel.author, funktion (err) om (err) res.status (500); res.json (typ: false, data:" Fel uppstod: "+ err) res.json (typ: true, data: "Kommentar:" + req.params.id + "updated")) exports.deleteComment = funktion (req, res, nästa) Article.findOneAndUpdate  "comments._id": new ObjectId (req.params.id), "$ pull": "kommentarer": "_id": nytt ObjectId (req.params.id), funktionen (fel, artikeln if (err) res.status (500); res.json (typ: false, data: "Fel uppstod:" + err) annat if (article) res.json sant, data: artikel) annat res.json (typ: false, data: "Kommentar:" + req.params.id + "not found"))

När du gör en förfrågan till en av resurs-URI: erna, kommer den relaterade funktionen som anges i regulatorn att exekveras. Varje funktion i kontrollerfilerna kan använda req och res objekt. De kommentar resursen här är en delresurs av Artikel. Alla frågeoperationer görs genom artikelmodellen för att hitta ett underdokument och göra den nödvändiga uppdateringen. Men när du försöker visa en kommentarresurs ser du en även om det inte finns någon samling i MongoDB.  

Andra designförslag

  • Välj lättförståeliga resurser för att ge användarna lättanvändning.
  • Låt affärslogiken genomföras av konsumenterna. Till exempel har artikelresursen ett fält som heter snigel. Konsumenter behöver inte skicka denna detalj till REST API. Denna slugstrategi bör hanteras på REST API-sidan för att minska kopplingen mellan API och konsumenter. Konsumenterna behöver bara skicka titeldetaljer, och du kan skapa sluggen enligt dina företagsbehov på REST API-sidan.
  • Implementera ett behörighetslager för dina API-slutpunkter. Obehöriga konsumenter kan få tillgång till begränsad data som tillhör en annan användare. I den här handledningen täckte vi inte användarresursen, men du kan referera till Token Based Authentication med AngularJS & NodeJS för mer information om API-autentiseringar.
  • Användar-URI i stället för frågesträng. / artiklar / 123  (Bra), / Artiklar? Id = 123 (Dålig).
  • Behåll inte staten använd alltid omedelbar inmatning / utgång.
  • Använd substantiv för dina resurser. Du kan använda HTTP-metoder för att kunna fungera på resurser.

Slutligen, om du utformar ett RESTful API genom att följa dessa grundläggande regler, kommer du alltid att ha ett flexibelt, underhållbart, lättförståeligt system.