[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-8zo9uhnXWON5t66Q5hQ6tmkk-ASgUW4YvOCsP3UX_eU":3,"sanity-vj3nWLvSTkFN1g2i4QAUEKzH-xNCF_j9c6Y8CULgoo4":1474},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":1449},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"d52b334e-4233-4e7d-a10b-bfba1472c2fb","2026-07-07T07:40:00.000Z",{"_type":10,"current":11},"slug","agent-orchestration-is-so-two-years-ago",null," Agent orchestration is so two-years ago",[15,21,27,33],{"_id":16,"publishedAt":17,"slug":18,"title":20},"eb5b66eb-9410-4329-83bb-22bbff39402a","2026-04-28T13:00:00.000Z",{"_type":10,"current":19},"turn-scattered-knowledge-into-trusted-intelligence","Turning scattered knowledge into trusted intelligence: Stack Internal 2026.3",{"_id":22,"publishedAt":23,"slug":24,"title":26},"369c2401-b62e-4a37-8ff8-bf603023ecad","2026-03-02T15:03:00.988Z",{"_type":10,"current":25},"what-s-new-at-stack-overflow-march-2026","What’s new at Stack Overflow: March 2026",{"_id":28,"publishedAt":29,"slug":30,"title":32},"5e9053a4-07ea-447c-91ea-29e0b6228537","2026-02-02T15:00:00.000Z",{"_type":10,"current":31},"what-s-new-at-stack-overflow-february-2026","What’s new at Stack Overflow: February 2026",{"_id":34,"publishedAt":35,"slug":36,"title":38},"a1b538eb-a8a6-46d0-80a1-ac70ec9bb935","2026-01-05T10:00:00.000-05:00",{"_type":10,"current":37},"what-s-new-at-stack-overflow-january-2026","What’s new at Stack Overflow: January 2026",{"_createdAt":40,"_id":41,"_rev":42,"_type":43,"_updatedAt":44,"author":45,"body":73,"comments":1402,"dateUrl":1403,"excerpt":1404,"image":1405,"legacyBody":1408,"product":12,"publishedAt":1411,"slug":1412,"sponsored":12,"tags":1414,"title":1448,"visible":1402},"2023-05-25T09:39:13Z","wp-post-15247","dgl3SCUzppW3U2LvCoSfa8","blogPost","2023-07-13T14:55:31Z",[46,62],{"_createdAt":47,"_id":48,"_rev":49,"_type":50,"_updatedAt":51,"avatar":52,"bio":57,"employee":58,"name":59,"slug":60},"2023-05-23T16:27:18Z","wp-author-cap-15245","07ZbrKPSUrjrV4wQ6fDpaa","blogAuthor","2023-06-20T15:05:10Z",{"_type":53,"asset":54},"image",{"_ref":55,"_type":56},"image-62a85a24c474f9bd150ed89c8d208ec43707ed61-302x302-jpg","reference","Web developer by day, blogger by night. ","none","John Au-Yeung",{"current":61},"john-au-yeung",{"_createdAt":47,"_id":63,"_rev":64,"_type":50,"_updatedAt":65,"avatar":66,"employee":69,"name":70,"slug":71},"wp-author-226","dgl3SCUzppW3U2LvCoOzcS","2023-06-20T15:05:06Z",{"_type":53,"asset":67},{"_ref":68,"_type":56},"image-56688f6337dd0a96034dfc998cdecc7810597d81-1024x1024-png","current","Ryan Donovan",{"current":72},"rdonovan",[74,97,105,124,137,148,159,170,181,192,203,214,225,234,242,260,267,297,305,313,345,353,361,403,406,426,449,456,464,472,480,488,496,504,512,531,577,589,592,600,608,616,624,647,650,705,728,736,752,764,771,779,787,795,803,811,819,827,846,854,862,865,881,912,920,928,935,943,951,959,967,970,994,1010,1018,1026,1029,1052,1083,1091,1099,1107,1139,1147,1155,1163,1171,1179,1187,1194,1202,1221,1240,1243,1266,1277,1285,1304,1311,1319,1327,1351,1359,1362,1370,1378,1386,1394],{"_key":75,"_type":76,"children":77,"markDefs":92,"style":96},"025346d0b79c","block",[78,83,88],{"_key":79,"_type":80,"marks":81,"text":82},"025346d0b79c0","span",[],"REST APIs are one of the most common kinds of web interfaces available today. They allow various clients including browser apps to communicate with services via the REST API. Therefore, it's very important to design REST APIs properly so that we won't run into problems down the road. We have to take into account ",{"_key":84,"_type":80,"marks":85,"text":87},"025346d0b79c1",[86],"cfbb1c45ea60","security",{"_key":89,"_type":80,"marks":90,"text":91},"025346d0b79c2",[],", performance, and ease of use for API consumers.",[93],{"_key":86,"_type":94,"href":95,"reference":12},"link","https:\u002F\u002Fstackoverflow.blog\u002F2021\u002F10\u002F06\u002Fbest-practices-for-authentication-and-authorization-for-rest-apis\u002F","normal",{"_key":98,"_type":76,"children":99,"markDefs":104,"style":96},"124689a387b5",[100],{"_key":101,"_type":80,"marks":102,"text":103},"124689a387b50",[],"Otherwise, we create problems for clients that use our APIs, which isn’t pleasant and detracts people from using our API. If we don’t follow commonly accepted conventions, then we confuse the maintainers of the API and the clients that use them since it’s different from what everyone expects.",[],{"_key":106,"_type":76,"children":107,"markDefs":121,"style":96},"0675dd5adf08",[108,112,117],{"_key":109,"_type":80,"marks":110,"text":111},"0675dd5adf080",[],"In this article, we'll look at how to design REST APIs to be easy to understand for anyone consuming them, ",{"_key":113,"_type":80,"marks":114,"text":116},"0675dd5adf081",[115],"ee0d320a5691","future-proof",{"_key":118,"_type":80,"marks":119,"text":120},"0675dd5adf082",[],", and secure and fast since they serve data to clients that may be confidential.",[122],{"_key":115,"_type":94,"href":123,"reference":12},"https:\u002F\u002Fstackoverflow.blog\u002F2022\u002F05\u002F19\u002Fcrystal-balls-and-clairvoyance-future-proofing-in-a-world-of-inevitable-change\u002F",{"_key":125,"_type":76,"children":126,"level":132,"listItem":133,"markDefs":134,"style":96},"f9cac68fee62",[127],{"_key":128,"_type":80,"marks":129,"text":131},"f9cac68fee620",[130],"2fdf946b9846","Accept and respond with JSON",1,"bullet",[135],{"_key":130,"_type":94,"href":136,"reference":12},"#h-accept-and-respond-with-json",{"_key":138,"_type":76,"children":139,"level":132,"listItem":133,"markDefs":145,"style":96},"d7bf27f49cd0",[140],{"_key":141,"_type":80,"marks":142,"text":144},"d7bf27f49cd00",[143],"abfed646a311","Use nouns instead of verbs in endpoint paths",[146],{"_key":143,"_type":94,"href":147,"reference":12},"#h-use-nouns-instead-of-verbs-in-endpoint-paths",{"_key":149,"_type":76,"children":150,"level":132,"listItem":133,"markDefs":156,"style":96},"558881ee5d94",[151],{"_key":152,"_type":80,"marks":153,"text":155},"558881ee5d940",[154],"9392814620e1","Name collections with plural nouns",[157],{"_key":154,"_type":94,"href":158,"reference":12},"#h-name-collections-with-plural-nouns",{"_key":160,"_type":76,"children":161,"level":132,"listItem":133,"markDefs":167,"style":96},"5b0910db47cb",[162],{"_key":163,"_type":80,"marks":164,"text":166},"5b0910db47cb0",[165],"c3cff4eb8e81","Nesting resources for hierarchical objects",[168],{"_key":165,"_type":94,"href":169,"reference":12},"#h-nesting-resources-for-hierarchical-objects",{"_key":171,"_type":76,"children":172,"level":132,"listItem":133,"markDefs":178,"style":96},"a14f84c195a9",[173],{"_key":174,"_type":80,"marks":175,"text":177},"a14f84c195a90",[176],"3ed39dea9a80","Handle errors gracefully and return standard error codes",[179],{"_key":176,"_type":94,"href":180,"reference":12},"#h-handle-errors-gracefully-and-return-standard-error-codes",{"_key":182,"_type":76,"children":183,"level":132,"listItem":133,"markDefs":189,"style":96},"a5c1e9b18416",[184],{"_key":185,"_type":80,"marks":186,"text":188},"a5c1e9b184160",[187],"82e3c20fbb4b","Allow filtering, sorting, and pagination",[190],{"_key":187,"_type":94,"href":191,"reference":12},"#h-allow-filtering-sorting-and-pagination",{"_key":193,"_type":76,"children":194,"level":132,"listItem":133,"markDefs":200,"style":96},"9422e190693c",[195],{"_key":196,"_type":80,"marks":197,"text":199},"9422e190693c0",[198],"b96287e2cd61","Maintain Good Security Practices",[201],{"_key":198,"_type":94,"href":202,"reference":12},"#h-maintain-good-security-practices",{"_key":204,"_type":76,"children":205,"level":132,"listItem":133,"markDefs":211,"style":96},"50e95b0da0f3",[206],{"_key":207,"_type":80,"marks":208,"text":210},"50e95b0da0f30",[209],"2a9a6e9f66d8","Cache data to improve performance",[212],{"_key":209,"_type":94,"href":213,"reference":12},"#h-cache-data-to-improve-performance",{"_key":215,"_type":76,"children":216,"level":132,"listItem":133,"markDefs":222,"style":96},"fdf7a3bc91ca",[217],{"_key":218,"_type":80,"marks":219,"text":221},"fdf7a3bc91ca0",[220],"76a44b45667c","Versioning our APIs",[223],{"_key":220,"_type":94,"href":224,"reference":12},"#h-versioning-our-apis",{"_key":226,"_type":76,"children":227,"markDefs":232,"style":233},"e36626c41498",[228],{"_key":229,"_type":80,"marks":230,"text":231},"e36626c414980",[],"What is a REST API?",[],"h2",{"_key":235,"_type":76,"children":236,"markDefs":241,"style":96},"b2712953a500",[237],{"_key":238,"_type":80,"marks":239,"text":240},"b2712953a5000",[],"A REST API is an application programming interface architecture style that conforms to specific architectural constraints, like stateless communication and cacheable data. It is not a protocol or standard. While REST APIs can be accessed through a number of communication protocols, most commonly, they are called over HTTPS, so the guidelines below apply to REST API endpoints that will be called over the internet.",[],{"_key":243,"_type":76,"children":244,"markDefs":258,"style":96},"067c4525dc5f",[245,249,254],{"_key":246,"_type":80,"marks":247,"text":248},"067c4525dc5f0",[],"Note: For REST APIs called over the internet, you'll like want to follow the ",{"_key":250,"_type":80,"marks":251,"text":253},"067c4525dc5f1",[252],"246e490dd048","best practices for REST API authentication",{"_key":255,"_type":80,"marks":256,"text":257},"067c4525dc5f2",[],".",[259],{"_key":252,"_type":94,"href":95,"reference":12},{"_key":261,"_type":76,"children":262,"markDefs":266,"style":233},"d17ee29325e7",[263],{"_key":264,"_type":80,"marks":265,"text":131},"d17ee29325e70",[],[],{"_key":268,"_type":76,"children":269,"markDefs":292,"style":96},"df53b682b8b8",[270,274,279,283,288],{"_key":271,"_type":80,"marks":272,"text":273},"df53b682b8b80",[],"Even though ",{"_key":275,"_type":80,"marks":276,"text":278},"df53b682b8b81",[277],"d6836b8a391d","some people think REST should only return hypertext",{"_key":280,"_type":80,"marks":281,"text":282},"df53b682b8b82",[]," (including ",{"_key":284,"_type":80,"marks":285,"text":287},"df53b682b8b83",[286],"7789933555e8","Roy Fielding",{"_key":289,"_type":80,"marks":290,"text":291},"df53b682b8b84",[]," who created the term) REST APIs should accept JSON for request payload and also send responses to JSON. JSON is the standard for transferring data. Almost every networked technology can use it: JavaScript has built-in methods to encode and decode JSON either through the Fetch API or another HTTP client. Server-side technologies have libraries that can decode JSON without doing much work.",[293,295],{"_key":277,"_type":94,"href":294,"reference":12},"https:\u002F\u002Fhtmx.org\u002Fessays\u002Fhow-did-rest-come-to-mean-the-opposite-of-rest\u002F",{"_key":286,"_type":94,"href":296,"reference":12},"https:\u002F\u002Froy.gbiv.com\u002Funtangled\u002F2008\u002Frest-apis-must-be-hypertext-driven",{"_key":298,"_type":76,"children":299,"markDefs":304,"style":96},"c53c3a881d6e",[300],{"_key":301,"_type":80,"marks":302,"text":303},"c53c3a881d6e0",[],"There are other ways to transfer data. XML isn’t widely supported by frameworks without transforming the data ourselves to something that can be used, and that’s usually JSON. We can’t manipulate this data as easily on the client-side, especially in browsers. It ends up being a lot of extra work just to do normal data transfer.",[],{"_key":306,"_type":76,"children":307,"markDefs":312,"style":96},"f2bffdb89203",[308],{"_key":309,"_type":80,"marks":310,"text":311},"f2bffdb892030",[],"Form data is good for sending data, especially if we want to send files. But for text and numbers, we don’t need form data to transfer those since—with most frameworks—we can transfer JSON by just getting the data from it directly on the client side. It’s by far the most straightforward to do so.",[],{"_key":314,"_type":76,"children":315,"markDefs":344,"style":96},"63c605b4d82d",[316,320,325,329,333,337,340],{"_key":317,"_type":80,"marks":318,"text":319},"63c605b4d82d0",[],"To make sure that when our REST API app responds with JSON that clients interpret it as such, we should set ",{"_key":321,"_type":80,"marks":322,"text":324},"63c605b4d82d1",[323],"code","Content-Type",{"_key":326,"_type":80,"marks":327,"text":328},"63c605b4d82d2",[]," in the response header to ",{"_key":330,"_type":80,"marks":331,"text":332},"63c605b4d82d3",[323],"application\u002Fjson",{"_key":334,"_type":80,"marks":335,"text":336},"63c605b4d82d4",[]," after the request is made. Many server-side app frameworks set the response header automatically. Some HTTP clients look at the ",{"_key":338,"_type":80,"marks":339,"text":324},"63c605b4d82d5",[323],{"_key":341,"_type":80,"marks":342,"text":343},"63c605b4d82d6",[]," response header and parse the data according to that format.",[],{"_key":346,"_type":76,"children":347,"markDefs":352,"style":96},"16beb6fbf901",[348],{"_key":349,"_type":80,"marks":350,"text":351},"16beb6fbf9010",[],"The only exception is if we’re trying to send and receive files between client and server. Then we need to handle file responses and send form data from client to server. But that is a topic for another time.\n",[],{"_key":354,"_type":76,"children":355,"markDefs":360,"style":96},"339473e0d324",[356],{"_key":357,"_type":80,"marks":358,"text":359},"339473e0d3240",[],"We should also make sure that our endpoints return JSON as a response. Many server-side frameworks have this as a built-in feature.\n",[],{"_key":362,"_type":76,"children":363,"markDefs":398,"style":96},"eb996f7df5c7",[364,368,373,377,382,386,390,394],{"_key":365,"_type":80,"marks":366,"text":367},"eb996f7df5c70",[],"Let’s take a look at an example API that accepts JSON payloads. This example will use the ",{"_key":369,"_type":80,"marks":370,"text":372},"eb996f7df5c71",[371],"bf75324c4cd7","Express",{"_key":374,"_type":80,"marks":375,"text":376},"eb996f7df5c72",[]," back end framework for Node.js. We can use the ",{"_key":378,"_type":80,"marks":379,"text":381},"eb996f7df5c73",[380,323],"3f7e2aff6cc0","body-parser",{"_key":383,"_type":80,"marks":384,"text":385},"eb996f7df5c74",[380]," middleware",{"_key":387,"_type":80,"marks":388,"text":389},"eb996f7df5c75",[]," to parse the JSON request body, and then we can call the ",{"_key":391,"_type":80,"marks":392,"text":393},"eb996f7df5c76",[323],"res.json",{"_key":395,"_type":80,"marks":396,"text":397},"eb996f7df5c77",[]," method with the object that we want to return as the JSON response as follows:\n",[399,401],{"_key":371,"_type":94,"href":400,"reference":12},"https:\u002F\u002Fexpressjs.com\u002F",{"_key":380,"_type":94,"href":402,"reference":12},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fbody-parser",{"_key":404,"_type":323,"code":405,"markDefs":12},"37256a487a65","const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.post('\u002F', (req, res) => {\n  res.json(req.body);\n});\n\napp.listen(3000, () => console.log('server started'));\n",{"_key":407,"_type":76,"children":408,"markDefs":425,"style":96},"dcd304dcfb1d",[409,413,417,421],{"_key":410,"_type":80,"marks":411,"text":412},"dcd304dcfb1d0",[323],"bodyParser.json()",{"_key":414,"_type":80,"marks":415,"text":416},"dcd304dcfb1d1",[]," parses the JSON request body string into a JavaScript object and then assigns it to the ",{"_key":418,"_type":80,"marks":419,"text":420},"dcd304dcfb1d2",[323],"req.body",{"_key":422,"_type":80,"marks":423,"text":424},"dcd304dcfb1d3",[]," object.\n",[],{"_key":427,"_type":76,"children":428,"markDefs":448,"style":96},"167eb03ddf7f",[429,433,436,440,444],{"_key":430,"_type":80,"marks":431,"text":432},"167eb03ddf7f0",[],"Set the ",{"_key":434,"_type":80,"marks":435,"text":324},"167eb03ddf7f1",[323],{"_key":437,"_type":80,"marks":438,"text":439},"167eb03ddf7f2",[]," header in the response to ",{"_key":441,"_type":80,"marks":442,"text":443},"167eb03ddf7f3",[323],"application\u002Fjson; charset=utf-8",{"_key":445,"_type":80,"marks":446,"text":447},"167eb03ddf7f4",[]," without any changes. The method above applies to most other back end frameworks.",[],{"_key":450,"_type":76,"children":451,"markDefs":455,"style":233},"2eb87cd076da",[452],{"_key":453,"_type":80,"marks":454,"text":144},"2eb87cd076da0",[],[],{"_key":457,"_type":76,"children":458,"markDefs":463,"style":96},"0ad782d52921",[459],{"_key":460,"_type":80,"marks":461,"text":462},"0ad782d529210",[],"We shouldn't use verbs in our endpoint paths. Instead, we should use the nouns which represent the entity that the endpoint that we're retrieving or manipulating as the pathname.\n",[],{"_key":465,"_type":76,"children":466,"markDefs":471,"style":96},"7d12b41bf559",[467],{"_key":468,"_type":80,"marks":469,"text":470},"7d12b41bf5590",[],"This is because our HTTP request method already has the verb. Having verbs in our API endpoint paths isn’t useful and it makes it unnecessarily long since it doesn’t convey any new information. The chosen verbs could vary by the developer’s whim. For instance, some like ‘get’ and some like ‘retrieve’, so it’s just better to let the HTTP GET verb tell us what and endpoint does.\n",[],{"_key":473,"_type":76,"children":474,"markDefs":479,"style":96},"a2b377bbbab0",[475],{"_key":476,"_type":80,"marks":477,"text":478},"a2b377bbbab00",[],"The action should be indicated by the HTTP request method that we're making. The most common methods include GET, POST, PUT, and DELETE.",[],{"_key":481,"_type":76,"children":482,"level":132,"listItem":133,"markDefs":487,"style":96},"6ad1babaaaf1",[483],{"_key":484,"_type":80,"marks":485,"text":486},"6ad1babaaaf10",[],"GET retrieves resources.",[],{"_key":489,"_type":76,"children":490,"level":132,"listItem":133,"markDefs":495,"style":96},"e27b0861c036",[491],{"_key":492,"_type":80,"marks":493,"text":494},"e27b0861c0360",[],"POST submits new data to the server.",[],{"_key":497,"_type":76,"children":498,"level":132,"listItem":133,"markDefs":503,"style":96},"d62e2de73304",[499],{"_key":500,"_type":80,"marks":501,"text":502},"d62e2de733040",[],"PUT updates existing data.",[],{"_key":505,"_type":76,"children":506,"level":132,"listItem":133,"markDefs":511,"style":96},"c73ad706e380",[507],{"_key":508,"_type":80,"marks":509,"text":510},"c73ad706e3800",[],"DELETE removes data.",[],{"_key":513,"_type":76,"children":514,"markDefs":528,"style":96},"ca8672826044",[515,519,524],{"_key":516,"_type":80,"marks":517,"text":518},"ca86728260440",[],"The verbs map to ",{"_key":520,"_type":80,"marks":521,"text":523},"ca86728260441",[522],"8b17c6eb7fc7","CRUD",{"_key":525,"_type":80,"marks":526,"text":527},"ca86728260442",[]," operations.",[529],{"_key":522,"_type":94,"href":530,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",{"_key":532,"_type":76,"children":533,"markDefs":576,"style":96},"fcd539fd9b52",[534,538,542,546,549,553,557,561,565,569,572],{"_key":535,"_type":80,"marks":536,"text":537},"fcd539fd9b520",[],"With the two principles we discussed above in mind, we should create routes like GET ",{"_key":539,"_type":80,"marks":540,"text":541},"fcd539fd9b521",[323],"\u002Farticles\u002F",{"_key":543,"_type":80,"marks":544,"text":545},"fcd539fd9b522",[]," for getting news articles. Likewise, POST ",{"_key":547,"_type":80,"marks":548,"text":541},"fcd539fd9b523",[323],{"_key":550,"_type":80,"marks":551,"text":552},"fcd539fd9b524",[]," is for adding a new article , PUT ",{"_key":554,"_type":80,"marks":555,"text":556},"fcd539fd9b525",[323],"\u002Farticles\u002F:id",{"_key":558,"_type":80,"marks":559,"text":560},"fcd539fd9b526",[]," is for updating the article with the given ",{"_key":562,"_type":80,"marks":563,"text":564},"fcd539fd9b527",[323],"id",{"_key":566,"_type":80,"marks":567,"text":568},"fcd539fd9b528",[],". DELETE ",{"_key":570,"_type":80,"marks":571,"text":556},"fcd539fd9b529",[323],{"_key":573,"_type":80,"marks":574,"text":575},"fcd539fd9b5210",[]," is for deleting an existing article with the given ID.\n",[],{"_key":578,"_type":76,"children":579,"markDefs":588,"style":96},"549893e66d71",[580,584],{"_key":581,"_type":80,"marks":582,"text":583},"549893e66d710",[323],"\u002Farticles",{"_key":585,"_type":80,"marks":586,"text":587},"549893e66d711",[]," represents a REST API resource. For instance, we can use Express to add the following endpoints for manipulate articles as follows:\n",[],{"_key":590,"_type":323,"code":591,"markDefs":12},"8f12c188d9a9","const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.get('\u002Farticles', (req, res) => {\n  const articles = [];\n  \u002F\u002F code to retrieve an article...\n  res.json(articles);\n});\n\napp.post('\u002Farticles', (req, res) => {\n  \u002F\u002F code to add a new article...\n  res.json(req.body);\n});\n\napp.put('\u002Farticles\u002F:id', (req, res) => {\n  const { id } = req.params;\n  \u002F\u002F code to update an article...\n  res.json(req.body);\n});\n\napp.delete('\u002Farticles\u002F:id', (req, res) => {\n  const { id } = req.params;\n  \u002F\u002F code to delete an article...\n  res.json({ deleted: id });\n});\n\napp.listen(3000, () => console.log('server started'));",{"_key":593,"_type":76,"children":594,"markDefs":599,"style":96},"544a9b7b8c65",[595],{"_key":596,"_type":80,"marks":597,"text":598},"544a9b7b8c650",[],"In the code above, we defined the endpoints to manipulate articles. As we can see, the path names do not have any verbs in them. All we have are nouns. The verbs are in the HTTP verbs.\n",[],{"_key":601,"_type":76,"children":602,"markDefs":607,"style":96},"8449aebe015f",[603],{"_key":604,"_type":80,"marks":605,"text":606},"8449aebe015f0",[],"The POST, PUT, and DELETE endpoints all take JSON as the request body, and they all return JSON as the response, including the GET endpoint.",[],{"_key":609,"_type":76,"children":610,"markDefs":615,"style":233},"8ec448c6df8c",[611],{"_key":612,"_type":80,"marks":613,"text":614},"8ec448c6df8c0",[],"Use logical nesting on endpoints",[],{"_key":617,"_type":76,"children":618,"markDefs":623,"style":96},"af92fa39aa87",[619],{"_key":620,"_type":80,"marks":621,"text":622},"af92fa39aa870",[],"When designing endpoints, it makes sense to group those that contain associated information. That is, if one object can contain another object, you should design the endpoint to reflect that. This is good practice regardless of whether your data is structured like this in your database. In fact, it may be advisable to avoid mirroring your database structure in your endpoints to avoid giving attackers unnecessary information.\n",[],{"_key":625,"_type":76,"children":626,"markDefs":646,"style":96},"c0cdac4b2678",[627,631,635,639,642],{"_key":628,"_type":80,"marks":629,"text":630},"c0cdac4b26780",[],"For example, if we want an endpoint to get the comments for a news article, we should append the ",{"_key":632,"_type":80,"marks":633,"text":634},"c0cdac4b26781",[323],"\u002Fcomments",{"_key":636,"_type":80,"marks":637,"text":638},"c0cdac4b26782",[]," path to the end of the ",{"_key":640,"_type":80,"marks":641,"text":583},"c0cdac4b26783",[323],{"_key":643,"_type":80,"marks":644,"text":645},"c0cdac4b26784",[]," path. We can do that with the following code in Express:\n",[],{"_key":648,"_type":323,"code":649,"markDefs":12},"bc17e3e0dfdd","const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.get('\u002Farticles\u002F:articleId\u002Fcomments', (req, res) => {\n  const { articleId } = req.params;\n  const comments = [];\n  \u002F\u002F code to get comments by articleId\n  res.json(comments);\n});\n\n\napp.listen(3000, () => console.log('server started'));",{"_key":651,"_type":76,"children":652,"markDefs":704,"style":96},"db818a8f4c7d",[653,657,661,665,669,673,677,681,685,689,693,697,700],{"_key":654,"_type":80,"marks":655,"text":656},"db818a8f4c7d0",[],"In the code above, we can use the GET method on the path ",{"_key":658,"_type":80,"marks":659,"text":660},"db818a8f4c7d1",[323],"'\u002Farticles\u002F:articleId\u002Fcomments'",{"_key":662,"_type":80,"marks":663,"text":664},"db818a8f4c7d2",[],". We get ",{"_key":666,"_type":80,"marks":667,"text":668},"db818a8f4c7d3",[323],"comments",{"_key":670,"_type":80,"marks":671,"text":672},"db818a8f4c7d4",[]," on the article identified by ",{"_key":674,"_type":80,"marks":675,"text":676},"db818a8f4c7d5",[323],"articleId",{"_key":678,"_type":80,"marks":679,"text":680},"db818a8f4c7d6",[]," and then return it in the response. We add ",{"_key":682,"_type":80,"marks":683,"text":684},"db818a8f4c7d7",[323],"'comments'",{"_key":686,"_type":80,"marks":687,"text":688},"db818a8f4c7d8",[]," after the ",{"_key":690,"_type":80,"marks":691,"text":692},"db818a8f4c7d9",[323],"'\u002Farticles\u002F:articleId'",{"_key":694,"_type":80,"marks":695,"text":696},"db818a8f4c7d10",[]," path segment to indicate that it's a child resource of ",{"_key":698,"_type":80,"marks":699,"text":583},"db818a8f4c7d11",[323],{"_key":701,"_type":80,"marks":702,"text":703},"db818a8f4c7d12",[],".\n",[],{"_key":706,"_type":76,"children":707,"markDefs":727,"style":96},"0206a3673a45",[708,712,715,719,723],{"_key":709,"_type":80,"marks":710,"text":711},"0206a3673a450",[],"This makes sense since ",{"_key":713,"_type":80,"marks":714,"text":668},"0206a3673a451",[323],{"_key":716,"_type":80,"marks":717,"text":718},"0206a3673a452",[]," are the children objects of the ",{"_key":720,"_type":80,"marks":721,"text":722},"0206a3673a453",[323],"articles",{"_key":724,"_type":80,"marks":725,"text":726},"0206a3673a454",[],", assuming each article has its own comments. Otherwise, it’s confusing to the user since this structure is generally accepted to be for accessing child objects. The same principle also applies to the POST, PUT, and DELETE endpoints. They can all use the same kind of nesting structure for the path names.",[],{"_key":729,"_type":76,"children":730,"markDefs":735,"style":96},"43bf42c51a1f",[731],{"_key":732,"_type":80,"marks":733,"text":734},"43bf42c51a1f0",[],"However, nesting can go too far. After about the second or third level, nested endpoints can get unwieldy. Consider, instead, returning the URL to those resources instead, especially if that data is not necessarily contained within the top level object.",[],{"_key":737,"_type":76,"children":738,"markDefs":751,"style":96},"2a8c1a61c726",[739,743,747],{"_key":740,"_type":80,"marks":741,"text":742},"2a8c1a61c7260",[],"For example, suppose you wanted to return the author of particular comments. You could use ",{"_key":744,"_type":80,"marks":745,"text":746},"2a8c1a61c7261",[323],"\u002Farticles\u002F:articleId\u002Fcomments\u002F:commentId\u002Fauthor",{"_key":748,"_type":80,"marks":749,"text":750},"2a8c1a61c7262",[],". But that's getting out of hand. Instead, return the URI for that particular user within the JSON response instead:",[],{"_key":753,"_type":76,"children":754,"markDefs":763,"style":96},"0c61cf2c2a32",[755,759],{"_key":756,"_type":80,"marks":757,"text":758},"0c61cf2c2a320",[323],"\"author\": \"\u002Fusers\u002F:userId\"",{"_key":760,"_type":80,"marks":761,"text":762},"0c61cf2c2a321",[],"\n",[],{"_key":765,"_type":76,"children":766,"markDefs":770,"style":233},"3f9e93268ff4",[767],{"_key":768,"_type":80,"marks":769,"text":177},"3f9e93268ff40",[],[],{"_key":772,"_type":76,"children":773,"markDefs":778,"style":96},"17e6114337f9",[774],{"_key":775,"_type":80,"marks":776,"text":777},"17e6114337f90",[],"To eliminate confusion for API users when an error occurs, we should handle errors gracefully and return HTTP response codes that indicate what kind of error occurred. This gives maintainers of the API enough information to understand the problem that’s occurred. We don’t want errors to bring down our system, so we can leave them unhandled, which means that the API consumer has to handle them.\n",[],{"_key":780,"_type":76,"children":781,"markDefs":786,"style":96},"59afe0919998",[782],{"_key":783,"_type":80,"marks":784,"text":785},"59afe09199980",[],"Common error HTTP status codes include:\n",[],{"_key":788,"_type":76,"children":789,"level":132,"listItem":133,"markDefs":794,"style":96},"d759a4546cd3",[790],{"_key":791,"_type":80,"marks":792,"text":793},"d759a4546cd30",[],"400 Bad Request - This means that client-side input fails validation.",[],{"_key":796,"_type":76,"children":797,"level":132,"listItem":133,"markDefs":802,"style":96},"9cf616bd2efe",[798],{"_key":799,"_type":80,"marks":800,"text":801},"9cf616bd2efe0",[],"401 Unauthorized - This means the user isn't not authorized to access a resource. It usually returns when the user isn't authenticated.",[],{"_key":804,"_type":76,"children":805,"level":132,"listItem":133,"markDefs":810,"style":96},"9c3407a4ff16",[806],{"_key":807,"_type":80,"marks":808,"text":809},"9c3407a4ff160",[],"403 Forbidden - This means the user is authenticated, but it's not allowed to access a resource.",[],{"_key":812,"_type":76,"children":813,"level":132,"listItem":133,"markDefs":818,"style":96},"03b93b4ec13a",[814],{"_key":815,"_type":80,"marks":816,"text":817},"03b93b4ec13a0",[],"404 Not Found - This indicates that a resource is not found.",[],{"_key":820,"_type":76,"children":821,"level":132,"listItem":133,"markDefs":826,"style":96},"68cd84645e92",[822],{"_key":823,"_type":80,"marks":824,"text":825},"68cd84645e920",[],"500 Internal server error - This is a generic server error. It probably shouldn't be thrown explicitly.",[],{"_key":828,"_type":76,"children":829,"level":132,"listItem":133,"markDefs":843,"style":96},"62560d6aa363",[830,834,839],{"_key":831,"_type":80,"marks":832,"text":833},"62560d6aa3630",[],"502 ",{"_key":835,"_type":80,"marks":836,"text":838},"62560d6aa3631",[837],"5f7953fa73d9","Bad Gateway",{"_key":840,"_type":80,"marks":841,"text":842},"62560d6aa3632",[]," - This indicates an invalid response from an upstream server.",[844],{"_key":837,"_type":94,"href":845,"reference":12},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FStatus\u002F502",{"_key":847,"_type":76,"children":848,"level":132,"listItem":133,"markDefs":853,"style":96},"ac7de1a25ab3",[849],{"_key":850,"_type":80,"marks":851,"text":852},"ac7de1a25ab30",[],"503 Service Unavailable - This indicates that something unexpected happened on server side (It can be anything like server overload, some parts of the system failed, etc.).",[],{"_key":855,"_type":76,"children":856,"markDefs":861,"style":96},"6c5430025d42",[857],{"_key":858,"_type":80,"marks":859,"text":860},"6c5430025d420",[],"We should be throwing errors that correspond to the problem that our app has encountered. For example, if we want to reject the data from the request payload, then we should return a 400 response as follows in an Express API:\n",[],{"_key":863,"_type":323,"code":864,"markDefs":12},"70ac6d112484","const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\n\u002F\u002F existing users\nconst users = [\n  { email: 'abc@foo.com' }\n]\n\napp.use(bodyParser.json());\n\napp.post('\u002Fusers', (req, res) => {\n  const { email } = req.body;\n  const userExists = users.find(u => u.email === email);\n  if (userExists) {\n    return res.status(400).json({ error: 'User already exists' })\n  }\n  res.json(req.body);\n});\n\n\napp.listen(3000, () => console.log('server started'));",{"_key":866,"_type":76,"children":867,"markDefs":880,"style":96},"e12a6ba0a3d3",[868,872,876],{"_key":869,"_type":80,"marks":870,"text":871},"e12a6ba0a3d30",[],"In the code above, we have a list of existing users in the ",{"_key":873,"_type":80,"marks":874,"text":875},"e12a6ba0a3d31",[323],"users",{"_key":877,"_type":80,"marks":878,"text":879},"e12a6ba0a3d32",[]," array with the given email.\n",[],{"_key":882,"_type":76,"children":883,"markDefs":911,"style":96},"650cd4eb9349",[884,888,892,896,899,903,907],{"_key":885,"_type":80,"marks":886,"text":887},"650cd4eb93490",[],"Then if we try to submit the payload with the ",{"_key":889,"_type":80,"marks":890,"text":891},"650cd4eb93491",[323],"email",{"_key":893,"_type":80,"marks":894,"text":895},"650cd4eb93492",[]," value that already exists in ",{"_key":897,"_type":80,"marks":898,"text":875},"650cd4eb93493",[323],{"_key":900,"_type":80,"marks":901,"text":902},"650cd4eb93494",[],", we'll get a 400 response status code with a ",{"_key":904,"_type":80,"marks":905,"text":906},"650cd4eb93495",[323],"'User already exists'",{"_key":908,"_type":80,"marks":909,"text":910},"650cd4eb93496",[]," message to let users know that the user already exists. With that information, the user can correct the action by changing the email to something that doesn't exist.\n",[],{"_key":913,"_type":76,"children":914,"markDefs":919,"style":96},"0457acceacad",[915],{"_key":916,"_type":80,"marks":917,"text":918},"0457acceacad0",[],"Error codes need to have messages accompanied with them so that the maintainers have enough information to troubleshoot the issue, but attackers can’t use the error content to carry our attacks like stealing information or bringing down the system.\n",[],{"_key":921,"_type":76,"children":922,"markDefs":927,"style":96},"17683fe4b5d1",[923],{"_key":924,"_type":80,"marks":925,"text":926},"17683fe4b5d10",[],"Whenever our API does not successfully complete, we should fail gracefully by sending an error with information to help users make corrective action.\n",[],{"_key":929,"_type":76,"children":930,"markDefs":934,"style":233},"ef7a235718c2",[931],{"_key":932,"_type":80,"marks":933,"text":188},"ef7a235718c20",[],[],{"_key":936,"_type":76,"children":937,"markDefs":942,"style":96},"afbe9b7c2034",[938],{"_key":939,"_type":80,"marks":940,"text":941},"afbe9b7c20340",[],"The databases behind a REST API can get very large. Sometimes, there's so much data that it shouldn’t be returned all at once because it’s way too slow or will bring down our systems. Therefore, we need ways to filter items.\n",[],{"_key":944,"_type":76,"children":945,"markDefs":950,"style":96},"d44027f03278",[946],{"_key":947,"_type":80,"marks":948,"text":949},"d44027f032780",[],"We also need ways to paginate data so that we only return a few results at a time. We don't want to tie up resources for too long by trying to get all the requested data at once.\n",[],{"_key":952,"_type":76,"children":953,"markDefs":958,"style":96},"6f3f92feebcb",[954],{"_key":955,"_type":80,"marks":956,"text":957},"6f3f92feebcb0",[],"Filtering and pagination both increase performance by reducing the usage of server resources. As more data accumulates in the database, the more important these features become.\n",[],{"_key":960,"_type":76,"children":961,"markDefs":966,"style":96},"a27cc34385ea",[962],{"_key":963,"_type":80,"marks":964,"text":965},"a27cc34385ea0",[],"Here’s a small example where an API can accept a query string with various query parameters to let us filter out items by their fields:\n",[],{"_key":968,"_type":323,"code":969,"markDefs":12},"5cec886e280e","const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\n\u002F\u002F employees data in a database\nconst employees = [\n  { firstName: 'Jane', lastName: 'Smith', age: 20 },\n  \u002F\u002F...\n  { firstName: 'John', lastName: 'Smith', age: 30 },\n  { firstName: 'Mary', lastName: 'Green', age: 50 },\n]\n\napp.use(bodyParser.json());\n\napp.get('\u002Femployees', (req, res) => {\n  const { firstName, lastName, age } = req.query;\n  let results = [...employees];\n  if (firstName) {\n    results = results.filter(r => r.firstName === firstName);\n  }\n\n  if (lastName) {\n    results = results.filter(r => r.lastName === lastName);\n  }\n\n  if (age) {\n    results = results.filter(r => +r.age === +age);\n  }\n  res.json(results);\n});\n\napp.listen(3000, () => console.log('server started'));",{"_key":971,"_type":76,"children":972,"markDefs":993,"style":96},"0782c69ad2aa",[973,977,981,985,989],{"_key":974,"_type":80,"marks":975,"text":976},"0782c69ad2aa0",[],"In the code above, we have the ",{"_key":978,"_type":80,"marks":979,"text":980},"0782c69ad2aa1",[323],"req.query",{"_key":982,"_type":80,"marks":983,"text":984},"0782c69ad2aa2",[]," variable to get the query parameters. We then extract the property values by destructuring the individual query parameters into variables using the JavaScript destructuring syntax. Finally, we run ",{"_key":986,"_type":80,"marks":987,"text":988},"0782c69ad2aa3",[323],"filter",{"_key":990,"_type":80,"marks":991,"text":992},"0782c69ad2aa4",[]," on with each query parameter value to locate the items that we want to return.\n",[],{"_key":995,"_type":76,"children":996,"markDefs":1009,"style":96},"a47ff4cd8d47",[997,1001,1005],{"_key":998,"_type":80,"marks":999,"text":1000},"a47ff4cd8d470",[],"Once we have done that, we return the ",{"_key":1002,"_type":80,"marks":1003,"text":1004},"a47ff4cd8d471",[323],"results",{"_key":1006,"_type":80,"marks":1007,"text":1008},"a47ff4cd8d472",[]," as the response. Therefore, when we make a GET request to the following path with the query string:\n",[],{"_key":1011,"_type":76,"children":1012,"markDefs":1017,"style":96},"02c8f007e34d",[1013],{"_key":1014,"_type":80,"marks":1015,"text":1016},"02c8f007e34d0",[323],"\u002Femployees?lastName=Smith&age=30",[],{"_key":1019,"_type":76,"children":1020,"markDefs":1025,"style":96},"f17dd2ab7769",[1021],{"_key":1022,"_type":80,"marks":1023,"text":1024},"f17dd2ab77690",[],"We get:\n",[],{"_key":1027,"_type":323,"code":1028,"markDefs":12},"f8908a233ad9","[\n    {\n        \"firstName\": \"John\",\n        \"lastName\": \"Smith\",\n        \"age\": 30\n    }\n]",{"_key":1030,"_type":76,"children":1031,"markDefs":1051,"style":96},"9c760fd23b02",[1032,1036,1040,1044,1048],{"_key":1033,"_type":80,"marks":1034,"text":1035},"9c760fd23b020",[],"as the returned response since we filtered by ",{"_key":1037,"_type":80,"marks":1038,"text":1039},"9c760fd23b021",[323],"lastName",{"_key":1041,"_type":80,"marks":1042,"text":1043},"9c760fd23b022",[]," and ",{"_key":1045,"_type":80,"marks":1046,"text":1047},"9c760fd23b023",[323],"age",{"_key":1049,"_type":80,"marks":1050,"text":703},"9c760fd23b024",[],[],{"_key":1053,"_type":76,"children":1054,"markDefs":1082,"style":96},"29c5d2d87baa",[1055,1059,1063,1067,1071,1075,1079],{"_key":1056,"_type":80,"marks":1057,"text":1058},"29c5d2d87baa0",[],"Likewise, we can accept the ",{"_key":1060,"_type":80,"marks":1061,"text":1062},"29c5d2d87baa1",[323],"page",{"_key":1064,"_type":80,"marks":1065,"text":1066},"29c5d2d87baa2",[]," query parameter and return a group of entries in the position from ",{"_key":1068,"_type":80,"marks":1069,"text":1070},"29c5d2d87baa3",[323],"(page - 1) * 20",{"_key":1072,"_type":80,"marks":1073,"text":1074},"29c5d2d87baa4",[]," to ",{"_key":1076,"_type":80,"marks":1077,"text":1078},"29c5d2d87baa5",[323],"page * 20",{"_key":1080,"_type":80,"marks":1081,"text":703},"29c5d2d87baa6",[],[],{"_key":1084,"_type":76,"children":1085,"markDefs":1090,"style":96},"f3289a0bac0b",[1086],{"_key":1087,"_type":80,"marks":1088,"text":1089},"f3289a0bac0b0",[],"We can also specify the fields to sort by in the query string. For instance, we can get the parameter from a query string with the fields we want to sort the data for. Then we can sort them by those individual fields.\n",[],{"_key":1092,"_type":76,"children":1093,"markDefs":1098,"style":96},"b3428cc0c60e",[1094],{"_key":1095,"_type":80,"marks":1096,"text":1097},"b3428cc0c60e0",[],"For instance, we may want to extract the query string from a URL like:\n",[],{"_key":1100,"_type":76,"children":1101,"markDefs":1106,"style":96},"84326a6bf6d7",[1102],{"_key":1103,"_type":80,"marks":1104,"text":1105},"84326a6bf6d70",[323],"http:\u002F\u002Fexample.com\u002Farticles?sort=+author,-datepublished",[],{"_key":1108,"_type":76,"children":1109,"markDefs":1138,"style":96},"c5808d404570",[1110,1114,1118,1122,1126,1130,1134],{"_key":1111,"_type":80,"marks":1112,"text":1113},"c5808d4045700",[],"Where ",{"_key":1115,"_type":80,"marks":1116,"text":1117},"c5808d4045701",[323],"+",{"_key":1119,"_type":80,"marks":1120,"text":1121},"c5808d4045702",[]," means ascending and ",{"_key":1123,"_type":80,"marks":1124,"text":1125},"c5808d4045703",[323],"-",{"_key":1127,"_type":80,"marks":1128,"text":1129},"c5808d4045704",[]," means descending. So we sort by author’s name in alphabetical order and ",{"_key":1131,"_type":80,"marks":1132,"text":1133},"c5808d4045705",[323],"datepublished",{"_key":1135,"_type":80,"marks":1136,"text":1137},"c5808d4045706",[]," from most recent to least recent.",[],{"_key":1140,"_type":76,"children":1141,"markDefs":1146,"style":233},"11bdd94690d6",[1142],{"_key":1143,"_type":80,"marks":1144,"text":1145},"11bdd94690d60",[],"Maintain good security practices",[],{"_key":1148,"_type":76,"children":1149,"markDefs":1154,"style":96},"3451bb0057a3",[1150],{"_key":1151,"_type":80,"marks":1152,"text":1153},"3451bb0057a30",[],"Most communication between client and server should be private since we often send and receive private information. Therefore, using SSL\u002FTLS for security is a must.",[],{"_key":1156,"_type":76,"children":1157,"markDefs":1162,"style":96},"6d45f4b3a988",[1158],{"_key":1159,"_type":80,"marks":1160,"text":1161},"6d45f4b3a9880",[],"A SSL certificate isn't too difficult to load onto a server and the cost is free or very low. There's no reason not to make our REST APIs communicate over secure channels instead of in the open.",[],{"_key":1164,"_type":76,"children":1165,"markDefs":1170,"style":96},"67492c5dfe6a",[1166],{"_key":1167,"_type":80,"marks":1168,"text":1169},"67492c5dfe6a0",[],"People shouldn't be able to access more information that they requested. For example, a normal user shouldn't be able to access information of another user. They also shouldn't be able to access data of admins.",[],{"_key":1172,"_type":76,"children":1173,"markDefs":1178,"style":96},"456a1116fed4",[1174],{"_key":1175,"_type":80,"marks":1176,"text":1177},"456a1116fed40",[],"To enforce the principle of least privilege, we need to add role checks either for a single role, or have more granular roles for each user.",[],{"_key":1180,"_type":76,"children":1181,"markDefs":1186,"style":96},"c25f2bff229a",[1182],{"_key":1183,"_type":80,"marks":1184,"text":1185},"c25f2bff229a0",[],"If we choose to group users into a few roles, then the roles should have the permissions that cover all they need and no more. If we have more granular permissions for each feature that users have access to, then we have to make sure that admins can add and remove those features from each user accordingly. Also, we need to add some preset roles that can be applied to a group users so that we don’t have to do that for every user manually.\n",[],{"_key":1188,"_type":76,"children":1189,"markDefs":1193,"style":233},"12f341718950",[1190],{"_key":1191,"_type":80,"marks":1192,"text":210},"12f3417189500",[],[],{"_key":1195,"_type":76,"children":1196,"markDefs":1201,"style":96},"81dd3a056bd7",[1197],{"_key":1198,"_type":80,"marks":1199,"text":1200},"81dd3a056bd70",[],"We can add caching to return data from the local memory cache instead of querying the database to get the data every time we want to retrieve some data that users request. The good thing about caching is that users can get data faster. However, the data that users get may be outdated. This may also lead to issues when debugging in production environments when something goes wrong as we keep seeing old data.",[],{"_key":1203,"_type":76,"children":1204,"markDefs":1218,"style":96},"7ec32d4d6035",[1205,1209,1214],{"_key":1206,"_type":80,"marks":1207,"text":1208},"7ec32d4d60350",[],"There are many kinds of caching solutions like ",{"_key":1210,"_type":80,"marks":1211,"text":1213},"7ec32d4d60351",[1212],"15ab0d62ee34","Redis",{"_key":1215,"_type":80,"marks":1216,"text":1217},"7ec32d4d60352",[],", in-memory caching, and more. We can change the way data is cached as our needs change.",[1219],{"_key":1212,"_type":94,"href":1220,"reference":12},"https:\u002F\u002Fredis.io\u002F",{"_key":1222,"_type":76,"children":1223,"markDefs":1237,"style":96},"0eaa96cc2178",[1224,1228,1233],{"_key":1225,"_type":80,"marks":1226,"text":1227},"0eaa96cc21780",[],"For instance, Express has the ",{"_key":1229,"_type":80,"marks":1230,"text":1232},"0eaa96cc21781",[1231,323],"3fa5da0aad4e","apicache",{"_key":1234,"_type":80,"marks":1235,"text":1236},"0eaa96cc21782",[]," middleware to add caching to our app without much configuration. We can add a simple in-memory cache into our server like so:\n",[1238],{"_key":1231,"_type":94,"href":1239,"reference":12},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fapicache",{"_key":1241,"_type":323,"code":1242,"markDefs":12},"643b05c0a1e0","const express = require('express');\nconst bodyParser = require('body-parser');\nconst apicache = require('apicache');\nconst app = express();\nlet cache = apicache.middleware;\napp.use(cache('5 minutes'));\n\n\u002F\u002F employees data in a database\nconst employees = [\n  { firstName: 'Jane', lastName: 'Smith', age: 20 },\n  \u002F\u002F...\n  { firstName: 'John', lastName: 'Smith', age: 30 },\n  { firstName: 'Mary', lastName: 'Green', age: 50 },\n]\n\napp.use(bodyParser.json());\n\napp.get('\u002Femployees', (req, res) => {\n  res.json(employees);\n});\n\napp.listen(3000, () => console.log('server started'));",{"_key":1244,"_type":76,"children":1245,"markDefs":1265,"style":96},"c5bbd9f318fe",[1246,1250,1253,1257,1261],{"_key":1247,"_type":80,"marks":1248,"text":1249},"c5bbd9f318fe0",[],"The code above just references the ",{"_key":1251,"_type":80,"marks":1252,"text":1232},"c5bbd9f318fe1",[323],{"_key":1254,"_type":80,"marks":1255,"text":1256},"c5bbd9f318fe2",[]," middleware with ",{"_key":1258,"_type":80,"marks":1259,"text":1260},"c5bbd9f318fe3",[323],"apicache.middleware",{"_key":1262,"_type":80,"marks":1263,"text":1264},"c5bbd9f318fe4",[]," and then we have:\n",[],{"_key":1267,"_type":76,"children":1268,"markDefs":1276,"style":96},"78f8b09b83e1",[1269,1273],{"_key":1270,"_type":80,"marks":1271,"text":1272},"78f8b09b83e10",[323],"app.use(cache('5 minutes'))",{"_key":1274,"_type":80,"marks":1275,"text":762},"78f8b09b83e11",[],[],{"_key":1278,"_type":76,"children":1279,"markDefs":1284,"style":96},"86c50b41b5ea",[1280],{"_key":1281,"_type":80,"marks":1282,"text":1283},"86c50b41b5ea0",[],"to apply the caching to the whole app. We cache the results for five minutes, for example. We can adjust this for our needs.",[],{"_key":1286,"_type":76,"children":1287,"markDefs":1301,"style":96},"efaf8670be4d",[1288,1292,1297],{"_key":1289,"_type":80,"marks":1290,"text":1291},"efaf8670be4d0",[],"If you are using caching, you should also include ",{"_key":1293,"_type":80,"marks":1294,"text":1296},"efaf8670be4d1",[323,1295],"e913c6639370","Cache-Control",{"_key":1298,"_type":80,"marks":1299,"text":1300},"efaf8670be4d2",[]," information in your headers. This will help users effectively use your caching system.",[1302],{"_key":1295,"_type":94,"href":1303,"reference":12},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FHeaders\u002FCache-Control",{"_key":1305,"_type":76,"children":1306,"markDefs":1310,"style":233},"4fa1a57e5843",[1307],{"_key":1308,"_type":80,"marks":1309,"text":221},"4fa1a57e58430",[],[],{"_key":1312,"_type":76,"children":1313,"markDefs":1318,"style":96},"b5b815a19e8d",[1314],{"_key":1315,"_type":80,"marks":1316,"text":1317},"b5b815a19e8d0",[],"We should have different versions of API if we're making any changes to them that may break clients. The versioning can be done according to semantic version (for example, 2.0.6 to indicate major version 2 and the sixth patch) like most apps do nowadays.\n",[],{"_key":1320,"_type":76,"children":1321,"markDefs":1326,"style":96},"82304b309b62",[1322],{"_key":1323,"_type":80,"marks":1324,"text":1325},"82304b309b620",[],"This way, we can gradually phase out old endpoints instead of forcing everyone to move to the new API at the same time. The v1 endpoint can stay active for people who don’t want to change, while the v2, with its shiny new features, can serve those who are ready to upgrade. This is especially important if our API is public. We should version them so that we won't break third party apps that use our APIs.\n",[],{"_key":1328,"_type":76,"children":1329,"markDefs":1350,"style":96},"a1d89e1587fb",[1330,1334,1338,1342,1346],{"_key":1331,"_type":80,"marks":1332,"text":1333},"a1d89e1587fb0",[],"Versioning is usually done with ",{"_key":1335,"_type":80,"marks":1336,"text":1337},"a1d89e1587fb1",[323],"\u002Fv1\u002F",{"_key":1339,"_type":80,"marks":1340,"text":1341},"a1d89e1587fb2",[],", ",{"_key":1343,"_type":80,"marks":1344,"text":1345},"a1d89e1587fb3",[323],"\u002Fv2\u002F",{"_key":1347,"_type":80,"marks":1348,"text":1349},"a1d89e1587fb4",[],", etc. added at the start of the API path.\n",[],{"_key":1352,"_type":76,"children":1353,"markDefs":1358,"style":96},"64f541c61597",[1354],{"_key":1355,"_type":80,"marks":1356,"text":1357},"64f541c615970",[],"For example, we can do that with Express as follows:\n",[],{"_key":1360,"_type":323,"code":1361,"markDefs":12},"221a9e28163c","const express = require('express');\nconst bodyParser = require('body-parser');\nconst app = express();\napp.use(bodyParser.json());\n\napp.get('\u002Fv1\u002Femployees', (req, res) => {\n  const employees = [];\n  \u002F\u002F code to get employees\n  res.json(employees);\n});\n\napp.get('\u002Fv2\u002Femployees', (req, res) => {\n  const employees = [];\n  \u002F\u002F different code to get employees\n  res.json(employees);\n});\n\napp.listen(3000, () => console.log('server started'));",{"_key":1363,"_type":76,"children":1364,"markDefs":1369,"style":96},"9f113f2419c2",[1365],{"_key":1366,"_type":80,"marks":1367,"text":1368},"9f113f2419c20",[],"We just add the version number to the start of the endpoint URL path to version them.\n",[],{"_key":1371,"_type":76,"children":1372,"markDefs":1377,"style":233},"601ef57898b2",[1373],{"_key":1374,"_type":80,"marks":1375,"text":1376},"601ef57898b20",[],"Conclusion",[],{"_key":1379,"_type":76,"children":1380,"markDefs":1385,"style":96},"1c358b517323",[1381],{"_key":1382,"_type":80,"marks":1383,"text":1384},"1c358b5173230",[],"The most important takeaways for designing high-quality REST APIs is to have consistency by following web standards and conventions. JSON, SSL\u002FTLS, and HTTP status codes are all standard building blocks of the modern web.\n",[],{"_key":1387,"_type":76,"children":1388,"markDefs":1393,"style":96},"88100c03e134",[1389],{"_key":1390,"_type":80,"marks":1391,"text":1392},"88100c03e1340",[],"Performance is also an important consideration. We can increase it by not returning too much data at once. Also, we can use caching so that we don't have to query for data all the time.\n",[],{"_key":1395,"_type":76,"children":1396,"markDefs":1401,"style":96},"6314f80b4d25",[1397],{"_key":1398,"_type":80,"marks":1399,"text":1400},"6314f80b4d250",[],"Paths of endpoints should be consistent, we use nouns only since the HTTP methods indicate the action we want to take. Paths of nested resources should come after the path of the parent resource. They should tell us what we’re getting or manipulating without the need to read extra documentation to understand what it’s doing.\n",[],true,"2020\u002F03\u002F02","In this article, we'll look at how to design REST APIs to be easy to understand for anyone consuming them, future-proof, and secure and fast since they serve data to clients that may be confidential.",{"_type":53,"asset":1406},{"_ref":1407,"_type":56},"image-e1eee67d0d8803936c09d3aba530bed2da974871-2309x1299-jpg",{"code":1409,"language":1410},"\u003C!-- wp:paragraph -->\n\u003Cp>REST APIs are one of the most common kinds of web interfaces available today. They allow various clients including browser apps to communicate with services via the REST API. Therefore, it's very important to design REST APIs properly so that we won't run into problems down the road. We have to take into account \u003Ca href=\"https:\u002F\u002Fstackoverflow.blog\u002F2021\u002F10\u002F06\u002Fbest-practices-for-authentication-and-authorization-for-rest-apis\u002F\">security\u003C\u002Fa>, performance, and ease of use for API consumers.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Otherwise, we create problems for clients that use our APIs, which isn’t pleasant and detracts people from using our API. If we don’t follow commonly accepted conventions, then we confuse the maintainers of the API and the clients that use them since it’s different from what everyone expects.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In this article, we'll look at how to design REST APIs to be easy to understand for anyone consuming them, \u003Ca href=\"https:\u002F\u002Fstackoverflow.blog\u002F2022\u002F05\u002F19\u002Fcrystal-balls-and-clairvoyance-future-proofing-in-a-world-of-inevitable-change\u002F\">future-proof\u003C\u002Fa>, and secure and fast since they serve data to clients that may be confidential. \u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-accept-and-respond-with-json\" data-level=\"2\">Accept and respond with JSON\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-use-nouns-instead-of-verbs-in-endpoint-paths\" data-level=\"2\">Use nouns instead of verbs in endpoint paths\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-name-collections-with-plural-nouns\" data-level=\"2\">Name collections with plural nouns\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-nesting-resources-for-hierarchical-objects\" data-level=\"2\">Nesting resources for hierarchical objects\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-handle-errors-gracefully-and-return-standard-error-codes\" data-level=\"2\">Handle errors gracefully and return standard error codes\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-allow-filtering-sorting-and-pagination\" data-level=\"2\">Allow filtering, sorting, and pagination\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-maintain-good-security-practices\" data-level=\"2\">Maintain Good Security Practices\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-cache-data-to-improve-performance\" data-level=\"2\">Cache data to improve performance\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Ca href=\"#h-versioning-our-apis\" data-level=\"2\">Versioning our APIs\u003C\u002Fa>\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-what-is-a-rest-api\">What is a REST API?\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp id=\"h-\">A REST API is an application programming interface architecture style that conforms to specific architectural constraints, like stateless communication and cacheable data. It is not a protocol or standard. While REST APIs can be accessed through a number of communication protocols, most commonly, they are called over HTTPS, so the guidelines below apply to REST API endpoints that will be called over the internet. \u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp id=\"h-\">Note: For REST APIs called over the internet, you'll like want to follow the \u003Ca href=\"https:\u002F\u002Fstackoverflow.blog\u002F2021\u002F10\u002F06\u002Fbest-practices-for-authentication-and-authorization-for-rest-apis\u002F\">best practices for REST API authentication\u003C\u002Fa>. \u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-accept-and-respond-with-json\">Accept and respond with JSON\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Even though \u003Ca href=\"https:\u002F\u002Fhtmx.org\u002Fessays\u002Fhow-did-rest-come-to-mean-the-opposite-of-rest\u002F\">some people think REST should only return hypertext\u003C\u002Fa> (including \u003Ca href=\"https:\u002F\u002Froy.gbiv.com\u002Funtangled\u002F2008\u002Frest-apis-must-be-hypertext-driven\">Roy Fielding\u003C\u002Fa> who created the term) REST APIs should accept JSON for request payload and also send responses to JSON. JSON is the standard for transferring data. Almost every networked technology can use it: JavaScript has built-in methods to encode and decode JSON either through the Fetch API or another HTTP client. Server-side technologies have libraries that can decode JSON without doing much work.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>There are other ways to transfer data. XML isn’t widely supported by frameworks without transforming the data ourselves to something that can be used, and that’s usually JSON. We can’t manipulate this data as easily on the client-side, especially in browsers. It ends up being a lot of extra work just to do normal data transfer.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Form data is good for sending data, especially if we want to send files. But for text and numbers, we don’t need form data to transfer those since—with most frameworks—we can transfer JSON by just getting the data from it directly on the client side. It’s by far the most straightforward to do so.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>To make sure that when our REST API app responds with JSON that clients interpret it as such, we should set \u003Ccode>Content-Type\u003C\u002Fcode> in the response header to \u003Ccode>application\u002Fjson\u003C\u002Fcode> after the request is made. Many server-side app frameworks set the response header automatically. Some HTTP clients look at the \u003Ccode>Content-Type\u003C\u002Fcode> response header and parse the data according to that format.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The only exception is if we’re trying to send and receive files between client and server. Then we need to handle file responses and send form data from client to server. But that is a topic for another time.&nbsp;\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We should also make sure that our endpoints return JSON as a response. Many server-side frameworks have this as a built-in feature.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Let’s take a look at an example API that accepts JSON payloads. This example will use the \u003Ca href=\"https:\u002F\u002Fexpressjs.com\u002F\">Express\u003C\u002Fa> back end framework for Node.js. We can use the \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fbody-parser\">\u003Ccode>body-parser\u003C\u002Fcode> middleware\u003C\u002Fa> to parse the JSON request body, and then we can call the \u003Ccode>res.json\u003C\u002Fcode> method with the object that we want to return as the JSON response as follows:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.post('\u002F', (req, res) =&gt; {\n&nbsp;&nbsp;res.json(req.body);\n});\n\napp.listen(3000, () =&gt; console.log('server started'));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>bodyParser.json()\u003C\u002Fcode> parses the JSON request body string into a JavaScript object and then assigns it to the \u003Ccode>req.body\u003C\u002Fcode> object.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Set the \u003Ccode>Content-Type\u003C\u002Fcode> header in the response to \u003Ccode>application\u002Fjson; charset=utf-8\u003C\u002Fcode> without any changes. The method above applies to most other back end frameworks.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:block {\"ref\":22100} \u002F-->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-use-nouns-instead-of-verbs-in-endpoint-paths\">Use nouns instead of verbs in endpoint paths\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We shouldn't use verbs in our endpoint paths. Instead, we should use the nouns which represent the entity that the endpoint that we're retrieving or manipulating as the pathname.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is because our HTTP request method already has the verb. Having verbs in our API endpoint paths isn’t useful and it makes it unnecessarily long since it doesn’t convey any new information. The chosen verbs could vary by the developer’s whim. For instance, some like ‘get’ and some like ‘retrieve’, so it’s just better to let the HTTP GET verb tell us what and endpoint does.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The action should be indicated by the HTTP request method that we're making. The most common methods include GET, POST, PUT, and DELETE.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>GET retrieves resources. \u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>POST submits new data to the server. \u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>PUT updates existing data. \u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>DELETE removes data. \u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The verbs map to \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete\">CRUD\u003C\u002Fa> operations.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>With the two principles we discussed above in mind, we should create routes like GET \u003Ccode>\u002Farticles\u002F\u003C\u002Fcode> for getting news articles. Likewise, POST \u003Ccode>\u002Farticles\u002F\u003C\u002Fcode> is for adding a new article , PUT \u003Ccode>\u002Farticles\u002F:id\u003C\u002Fcode> is for updating the article with the given \u003Ccode>id\u003C\u002Fcode>. DELETE \u003Ccode>\u002Farticles\u002F:id\u003C\u002Fcode> is for deleting an existing article with the given ID.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>\u002Farticles\u003C\u002Fcode> represents a REST API resource. For instance, we can use Express to add the following endpoints for manipulate articles as follows:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.get('\u002Farticles', (req, res) =&gt; {\n&nbsp;&nbsp;const articles = &#91;];\n&nbsp;&nbsp;\u002F\u002F code to retrieve an article...\n&nbsp;&nbsp;res.json(articles);\n});\n\napp.post('\u002Farticles', (req, res) =&gt; {\n&nbsp;&nbsp;\u002F\u002F code to add a new article...\n&nbsp;&nbsp;res.json(req.body);\n});\n\napp.put('\u002Farticles\u002F:id', (req, res) =&gt; {\n&nbsp;&nbsp;const { id } = req.params;\n&nbsp;&nbsp;\u002F\u002F code to update an article...\n&nbsp;&nbsp;res.json(req.body);\n});\n\napp.delete('\u002Farticles\u002F:id', (req, res) =&gt; {\n&nbsp;&nbsp;const { id } = req.params;\n&nbsp;&nbsp;\u002F\u002F code to delete an article...\n&nbsp;&nbsp;res.json({ deleted: id });\n});\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In the code above, we defined the endpoints to manipulate articles. As we can see, the path names do not have any verbs in them. All we have are nouns. The verbs are in the HTTP verbs.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The POST, PUT, and DELETE endpoints all take JSON as the request body, and they all return JSON as the response, including the GET endpoint.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-use-logical-nesting-on-endpoints\">Use logical nesting on endpoints\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When designing endpoints, it makes sense to group those that contain associated information. That is, if one object can contain another object, you should design the endpoint to reflect that. This is good practice regardless of whether your data is structured like this in your database. In fact, it may be advisable to avoid mirroring your database structure in your endpoints to avoid giving attackers unnecessary information. \u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>For example, if we want an endpoint to get the comments for a news article, we should append the \u003Ccode>\u002Fcomments\u003C\u002Fcode> path to the end of the \u003Ccode>\u002Farticles\u003C\u002Fcode> path. We can do that with the following code in Express:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\napp.use(bodyParser.json());\n\napp.get('\u002Farticles\u002F:articleId\u002Fcomments', (req, res) =&gt; {\n&nbsp;&nbsp;const { articleId } = req.params;\n&nbsp;&nbsp;const comments = &#91;];\n&nbsp;&nbsp;\u002F\u002F code to get comments by articleId\n&nbsp;&nbsp;res.json(comments);\n});\n\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In the code above, we can use the GET method on the path \u003Ccode>'\u002Farticles\u002F:articleId\u002Fcomments'\u003C\u002Fcode>. We get \u003Ccode>comments\u003C\u002Fcode> on the article identified by \u003Ccode>articleId\u003C\u002Fcode> and then return it in the response. We add \u003Ccode>'comments'\u003C\u002Fcode> after the \u003Ccode>'\u002Farticles\u002F:articleId'\u003C\u002Fcode> path segment to indicate that it's a child resource of \u003Ccode>\u002Farticles\u003C\u002Fcode>.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This makes sense since \u003Ccode>comments\u003C\u002Fcode> are the children objects of the \u003Ccode>articles\u003C\u002Fcode>, assuming each article has its own comments. Otherwise, it’s confusing to the user since this structure is generally accepted to be for accessing child objects. The same principle also applies to the POST, PUT, and DELETE endpoints. They can all use the same kind of nesting structure for the path names.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>However, nesting can go too far. After about the second or third level, nested endpoints can get unwieldy. Consider, instead, returning the URL to those resources instead, especially if that data is not necessarily contained within the top level object. \u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>For example, suppose you wanted to return the author of particular comments. You could use \u003Ccode>\u002Farticles\u002F:articleId\u002Fcomments\u002F:commentId\u002Fauthor\u003C\u002Fcode>. But that's getting out of hand. Instead, return the URI for that particular user within the JSON response instead:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>\"author\": \"\u002Fusers\u002F:userId\"\u003C\u002Fcode>\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-handle-errors-gracefully-and-return-standard-error-codes\">Handle errors gracefully and return standard error codes\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>To eliminate confusion for API users when an error occurs, we should handle errors gracefully and return HTTP response codes that indicate what kind of error occurred. This gives maintainers of the API enough information to understand the problem that’s occurred. We don’t want errors to bring down our system, so we can leave them unhandled, which means that the API consumer has to handle them.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Common error HTTP status codes include:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>400 Bad Request - This means that client-side input fails validation.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>401 Unauthorized - This means the user isn't not authorized to access a resource. It usually returns when the user isn't authenticated.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>403 Forbidden - This means the user is authenticated, but it's not allowed to access a resource.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>404 Not Found - This indicates that a resource is not found.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>500 Internal server error - This is a generic server error. It probably shouldn't be thrown explicitly.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>502 \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FStatus\u002F502\">Bad Gateway\u003C\u002Fa> - This indicates an invalid response from an upstream server.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>503 Service Unavailable - This indicates that something unexpected happened on server side (It can be anything like server overload, some parts of the system failed, etc.).\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We should be throwing errors that correspond to the problem that our app has encountered. For example, if we want to reject the data from the request payload, then we should return a 400 response as follows in an Express API:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\n\u002F\u002F existing users\nconst users = &#91;\n&nbsp;&nbsp;{ email: 'abc@foo.com' }\n]\n\napp.use(bodyParser.json());\n\napp.post('\u002Fusers', (req, res) =&gt; {\n&nbsp;&nbsp;const { email } = req.body;\n&nbsp;&nbsp;const userExists = users.find(u =&gt; u.email === email);\n&nbsp;&nbsp;if (userExists) {\n&nbsp;&nbsp;&nbsp;&nbsp;return res.status(400).json({ error: 'User already exists' })\n&nbsp;&nbsp;}\n&nbsp;&nbsp;res.json(req.body);\n});\n\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In the code above, we have a list of existing users in the \u003Ccode>users\u003C\u002Fcode> array with the given email.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Then if we try to submit the payload with the \u003Ccode>email\u003C\u002Fcode> value that already exists in \u003Ccode>users\u003C\u002Fcode>, we'll get a 400 response status code with a \u003Ccode>'User already exists'\u003C\u002Fcode> message to let users know that the user already exists. With that information, the user can correct the action by changing the email to something that doesn't exist.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Error codes need to have messages accompanied with them so that the maintainers have enough information to troubleshoot the issue, but attackers can’t use the error content to carry our attacks like stealing information or bringing down the system.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Whenever our API does not successfully complete, we should fail gracefully by sending an error with information to help users make corrective action.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-allow-filtering-sorting-and-pagination\">Allow filtering, sorting, and pagination\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The databases behind a REST API can get very large. Sometimes, there's so much data that it shouldn’t be returned all at once because it’s way too slow or will bring down our systems. Therefore, we need ways to filter items.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We also need ways to paginate data so that we only return a few results at a time. We don't want to tie up resources for too long by trying to get all the requested data at once.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Filtering and pagination both increase performance by reducing the usage of server resources. As more data accumulates in the database, the more important these features become.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Here’s a small example where an API can accept a query string with various query parameters to let us filter out items by their fields:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\n\nconst app = express();\n\n\u002F\u002F employees data in a database\nconst employees = &#91;\n&nbsp;&nbsp;{ firstName: 'Jane', lastName: 'Smith', age: 20 },\n&nbsp;&nbsp;\u002F\u002F...\n&nbsp;&nbsp;{ firstName: 'John', lastName: 'Smith', age: 30 },\n&nbsp;&nbsp;{ firstName: 'Mary', lastName: 'Green', age: 50 },\n]\n\napp.use(bodyParser.json());\n\napp.get('\u002Femployees', (req, res) =&gt; {\n&nbsp;&nbsp;const { firstName, lastName, age } = req.query;\n&nbsp;&nbsp;let results = &#91;...employees];\n&nbsp;&nbsp;if (firstName) {\n&nbsp;&nbsp;&nbsp;&nbsp;results = results.filter(r =&gt; r.firstName === firstName);\n&nbsp;&nbsp;}\n\n&nbsp;&nbsp;if (lastName) {\n&nbsp;&nbsp;&nbsp;&nbsp;results = results.filter(r =&gt; r.lastName === lastName);\n&nbsp;&nbsp;}\n\n&nbsp;&nbsp;if (age) {\n&nbsp;&nbsp;&nbsp;&nbsp;results = results.filter(r =&gt; +r.age === +age);\n&nbsp;&nbsp;}\n&nbsp;&nbsp;res.json(results);\n});\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In the code above, we have the \u003Ccode>req.query\u003C\u002Fcode> variable to get the query parameters. We then extract the property values by destructuring the individual query parameters into variables using the JavaScript destructuring syntax. Finally, we run \u003Ccode>filter\u003C\u002Fcode> on with each query parameter value to locate the items that we want to return.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Once we have done that, we return the \u003Ccode>results\u003C\u002Fcode> as the response. Therefore, when we make a GET request to the following path with the query string:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>\u002Femployees?lastName=Smith&amp;age=30\u003C\u002Fcode>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We get:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>&#91;\n&nbsp;&nbsp;&nbsp;&nbsp;{\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\"firstName\": \"John\",\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\"lastName\": \"Smith\",\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\"age\": 30\n&nbsp;&nbsp;&nbsp;&nbsp;}\n]\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>as the returned response since we filtered by \u003Ccode>lastName\u003C\u002Fcode> and \u003Ccode>age\u003C\u002Fcode>.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Likewise, we can accept the \u003Ccode>page\u003C\u002Fcode> query parameter and return a group of entries in the position from \u003Ccode>(page - 1) * 20\u003C\u002Fcode> to \u003Ccode>page * 20\u003C\u002Fcode>.&nbsp;\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We can also specify the fields to sort by in the query string. For instance, we can get the parameter from a query string with the fields we want to sort the data for. Then we can sort them by those individual fields.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>For instance, we may want to extract the query string from a URL like:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>http:\u002F\u002Fexample.com\u002Farticles?sort=+author,-datepublished\u003C\u002Fcode>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Where \u003Ccode>+\u003C\u002Fcode> means ascending and \u003Ccode>-\u003C\u002Fcode> means descending. So we sort by author’s name in alphabetical order and \u003Ccode>datepublished\u003C\u002Fcode> from most recent to least recent.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-maintain-good-security-practices\">Maintain good security practices\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Most communication between client and server should be private since we often send and receive private information. Therefore, using SSL\u002FTLS for security is a must.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>A SSL certificate isn't too difficult to load onto a server and the cost is free or very low. There's no reason not to make our REST APIs communicate over secure channels instead of in the open.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>People shouldn't be able to access more information that they requested. For example, a normal user shouldn't be able to access information of another user. They also shouldn't be able to access data of admins.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>To enforce the principle of least privilege, we need to add role checks either for a single role, or have more granular roles for each user.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>If we choose to group users into a few roles, then the roles should have the permissions that cover all they need and no more. If we have more granular permissions for each feature that users have access to, then we have to make sure that admins can add and remove those features from each user accordingly. Also, we need to add some preset roles that can be applied to a group users so that we don’t have to do that for every user manually.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-cache-data-to-improve-performance\">Cache data to improve performance\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We can add caching to return data from the local memory cache instead of querying the database to get the data every time we want to retrieve some data that users request. The good thing about caching is that users can get data faster. However, the data that users get may be outdated. This may also lead to issues when debugging in production environments when something goes wrong as we keep seeing old data.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>There are many kinds of caching solutions like \u003Ca href=\"https:\u002F\u002Fredis.io\u002F\">Redis\u003C\u002Fa>, in-memory caching, and more. We can change the way data is cached as our needs change.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>For instance, Express has the \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fapicache\">\u003Ccode>apicache\u003C\u002Fcode>\u003C\u002Fa> middleware to add caching to our app without much configuration. We can add a simple in-memory cache into our server like so:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\nconst apicache = require('apicache');\nconst app = express();\nlet cache = apicache.middleware;\napp.use(cache('5 minutes'));\n\n\u002F\u002F employees data in a database\nconst employees = &#91;\n&nbsp;&nbsp;{ firstName: 'Jane', lastName: 'Smith', age: 20 },\n&nbsp;&nbsp;\u002F\u002F...\n&nbsp;&nbsp;{ firstName: 'John', lastName: 'Smith', age: 30 },\n&nbsp;&nbsp;{ firstName: 'Mary', lastName: 'Green', age: 50 },\n]\n\napp.use(bodyParser.json());\n\napp.get('\u002Femployees', (req, res) =&gt; {\n&nbsp;&nbsp;res.json(employees);\n});\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The code above just references the \u003Ccode>apicache\u003C\u002Fcode> middleware with \u003Ccode>apicache.middleware\u003C\u002Fcode> and then we have:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>app.use(cache('5 minutes'))\u003C\u002Fcode>\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>to apply the caching to the whole app. We cache the results for five minutes, for example. We can adjust this for our needs.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>If you are using caching, you should also include \u003Ccode>\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FHeaders\u002FCache-Control\">Cache-Control\u003C\u002Fa>\u003C\u002Fcode> information in your headers. This will help users effectively use your caching system. \u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-versioning-our-apis\">Versioning our APIs\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We should have different versions of API if we're making any changes to them that may break clients. The versioning can be done according to semantic version (for example, 2.0.6 to indicate major version 2 and the sixth patch) like most apps do nowadays.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This way, we can gradually phase out old endpoints instead of forcing everyone to move to the new API at the same time. The v1 endpoint can stay active for people who don’t want to change, while the v2, with its shiny new features, can serve those who are ready to upgrade. This is especially important if our API is public. We should version them so that we won't break third party apps that use our APIs.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Versioning is usually done with \u003Ccode>\u002Fv1\u002F\u003C\u002Fcode>, \u003Ccode>\u002Fv2\u002F\u003C\u002Fcode>, etc. added at the start of the API path.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>For example, we can do that with Express as follows:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const express = require('express');\nconst bodyParser = require('body-parser');\nconst app = express();\napp.use(bodyParser.json());\n\napp.get('\u002Fv1\u002Femployees', (req, res) =&gt; {\n&nbsp;&nbsp;const employees = &#91;];\n&nbsp;&nbsp;\u002F\u002F code to get employees\n&nbsp;&nbsp;res.json(employees);\n});\n\napp.get('\u002Fv2\u002Femployees', (req, res) =&gt; {\n&nbsp;&nbsp;const employees = &#91;];\n&nbsp;&nbsp;\u002F\u002F different code to get employees\n&nbsp;&nbsp;res.json(employees);\n});\n\napp.listen(3000, () =&gt; console.log('server started'));\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We just add the version number to the start of the endpoint URL path to version them.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The most important takeaways for designing high-quality REST APIs is to have consistency by following web standards and conventions. JSON, SSL\u002FTLS, and HTTP status codes are all standard building blocks of the modern web.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Performance is also an important consideration. We can increase it by not returning too much data at once. Also, we can use caching so that we don't have to query for data all the time.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Paths of endpoints should be consistent, we use nouns only since the HTTP methods indicate the action we want to take. Paths of nested resources should come after the path of the parent resource. They should tell us what we’re getting or manipulating without the need to read extra documentation to understand what it’s doing.\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->","html","2020-03-02T14:55:01.000Z",{"current":1413},"best-practices-for-rest-api-design",[1415,1423,1428,1432,1436,1441,1446],{"_createdAt":1416,"_id":1417,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1420,"title":1422},"2023-05-23T16:43:21Z","wp-tagcat-bulletin","9HpbCsT2tq0xwozQfkc4ih","blogTag",{"current":1421},"bulletin","Bulletin",{"_createdAt":1416,"_id":1424,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1425,"title":1427},"wp-tagcat-code-for-a-living",{"current":1426},"code-for-a-living","Code for a Living",{"_createdAt":1416,"_id":1429,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1430,"title":1431},"wp-tagcat-express",{"current":1431},"express",{"_createdAt":1416,"_id":1433,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1434,"title":1435},"wp-tagcat-javascript",{"current":1435},"javascript",{"_createdAt":1416,"_id":1437,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1438,"title":1440},"wp-tagcat-rest-api",{"current":1439},"rest-api","rest api",{"_createdAt":1416,"_id":1442,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1443,"title":1445},"wp-tagcat-stackoverflow",{"current":1444},"stackoverflow","Stackoverflow",{"_createdAt":1416,"_id":1442,"_rev":1418,"_type":1419,"_updatedAt":1416,"slug":1447,"title":1445},{"current":1444},"Best practices for REST API design",[1450,1456,1462,1468],{"_id":1451,"publishedAt":1452,"slug":1453,"sponsored":12,"title":1455},"76c9771b-34e6-4d98-8641-ecefc711f0ef","2026-07-06T15:23:34.559Z",{"_type":10,"current":1454},"when-the-sensor-starts-thinking-snortml-agentic-ai-and-the-evolving-architecture-of-intrusion-detection","When the sensor starts thinking: SnortML, agentic AI, and the evolving architecture of intrusion detection",{"_id":1457,"publishedAt":1458,"slug":1459,"sponsored":12,"title":1461},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":1460},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":1463,"publishedAt":1464,"slug":1465,"sponsored":12,"title":1467},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":1466},"your-ai-shipped-a-backend-that-boots-that-is-the-whole-problem","Your AI shipped a backend that boots. That is the whole problem.",{"_id":1469,"publishedAt":1470,"slug":1471,"sponsored":12,"title":1473},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":1472},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"data":1475,"sourceMap":-1},{"count":1476,"lastTimestamp":1477},101,"2025-02-12T19:34:45Z"]