{"id":3373,"date":"2022-03-22T13:05:06","date_gmt":"2022-03-22T13:05:06","guid":{"rendered":"http:\/\/the-codest.localhost\/blog\/including-sub-resources-in-a-rest-ish-api\/"},"modified":"2026-04-28T14:05:30","modified_gmt":"2026-04-28T14:05:30","slug":"inkludera-underresurser-i-en-rest-ish-api","status":"publish","type":"post","link":"https:\/\/thecodest.co\/sv\/blog\/including-sub-resources-in-a-rest-ish-api\/","title":{"rendered":"Inkludera underresurser i ett REST-liknande API"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">What will we do?<\/h2>\n\n\n\n<p>We will build a bookshelf app to list books with (or without) authors <a href=\"https:\/\/thecodest.co\/sv\/blog\/app-data-collection-security-risks-value-and-types-explored\/\">data<\/a>. There will be a single <code>#index<\/code> action and some seeds. This will be an example app to show how you can give a user control on included <strong>sub-resources in a REST-ish <a href=\"https:\/\/thecodest.co\/sv\/blog\/compare-staff-augmentation-firms-that-excel-in-api-team-staffing-for-financial-technology-projects\/\">API<\/a><\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">&#8220;Acceptance Criteria&#8221;<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>User can list the books.<\/li>\n\n\n\n<li>User can pass <code>includes<\/code> query parameter to load associated resources (<code>author<\/code>).<\/li>\n\n\n\n<li><code>includes<\/code> query parameter has a format of string: comma separated words, representing nested resources.<\/li>\n\n\n\n<li>We should have some constants that define which resources are includeable for which action.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Tools<\/h2>\n\n\n\n<p>We will use <code>blueprinter<\/code> as a serializer, because it&#8217;s format agnostic and quite flexible. This is an only gem we will add to rails&#8217; standard toolset.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The app<\/h2>\n\n\n\n<p>Let&#8217;s create an example app. We&#8217;re not adding test framework as it&#8217;s out of our scope.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\"><a href=\"https:\/\/thecodest.co\/sv\/blog\/ways-to-increase-your-rails-performance\/\">rails<\/a> new bookshelf -T<\/code><\/pre>\n\n\n\n<p>Now create <code>Author<\/code> model:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">rails g model author name:string\n#=&gt; invoke  active_record\n#=&gt; create    db\/migrate\/20211224084524_create_authors.rb\n#=&gt; create    app\/models\/author.rb<\/code><\/pre>\n\n\n\n<p>And <code>Book<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">rails g model book author:references title:string\n# =&gt; invoke  active_record\n# =&gt; create    db\/migrate\/20211224084614_create_books.rb\n# =&gt; create    app\/models\/book.rb<\/code><\/pre>\n\n\n\n<p>We will need some seeds:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># db\/seeds.rb\n\ndumas = Author.create(name: 'Alexandre Dumas')\nlewis = Author.create(name: 'C.S. Lewis')\nmartin = Author.create(name: 'Robert C. Martin')\n\nBook.create(author: dumas, title: 'The Three Musketeers')\nBook.create(author: lewis, title: 'The Lion, the Witch and the Wardrobe')\nBook.create(author: martin, title: 'Clean Code')<\/code><\/pre>\n\n\n\n<p>And now we are ready to run migrations and seed the db:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">rails db:migrate &amp;&amp; rails db:seed<\/code><\/pre>\n\n\n\n<p>Let&#8217;s add <code>has_many<\/code> for books in <code>Author<\/code> model:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/models\/author.rb\n\nclass Author &lt; ApplicationRecord\n  has_many :books\nend<\/code><\/pre>\n\n\n\n<p>It&#8217;s time to write a controller that will return our data. We will use <code>API<\/code> namespace, so first let&#8217;s add an acronym to inflections:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># config\/initializers\/inflections.rb\n\nActiveSupport::Inflector.inflections(:en) do |inflect|\n  inflect.acronym 'API'\nend<\/code><\/pre>\n\n\n\n<p>Ok, let&#8217;s add our serializer to <code>Gemfile<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># Add to Gemfile\n\ngem 'blueprinter'\n\n<\/code><\/pre>\n\n\n\n<p>And of course install it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">bundle install<\/code><\/pre>\n\n\n\n<p>Then we can build our blueprints:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/blueprints\/author_blueprint.rb\n\nclass AuthorBlueprint &lt; Blueprinter::Base\n  identifier :id\n\n  fields :name\nend<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/blueprints\/book_blueprint.rb\n\nclass BookBlueprint &lt; Blueprinter::Base\n  identifier :id\n\n  fields :title\n\n  association :author, blueprint: AuthorBlueprint\nend<\/code><\/pre>\n\n\n\n<p>Add a base controller for <code>API<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/controllers\/api\/v1\/base_controller.rb\n\nmodule API\n  module V1\n    class BaseController &lt; ActionController::API\n    end\n  end\nend<\/code><\/pre>\n\n\n\n<p>And the draft version of our <code>BooksController<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/controllers\/api\/v1\/books_controller.rb\n\nmodule API\n  module V1\n    class BooksController &lt; BaseController\n      def index\n        books = Book.all\n\n        render json: BookBlueprint.render(books)\n      end\n    end\n  end\nend<\/code><\/pre>\n\n\n\n<p>We also must define routing of course:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># config\/routes.rb\n\nRails.application.routes.draw do\n  namespace :api do\n    namespace :v1 do\n      resources :books, only: :index\n    end\n  end\nend<\/code><\/pre>\n\n\n\n<p>Let&#8217;s test what we&#8217;ve done so far:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">rails s<span style=\"background-color: initial; font-family: inherit; font-size: inherit;\"> <\/span><\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">curl http:\/\/localhost:3000\/api\/v1\/books\n\n# => [{\"id\":1,\"author\":{\"id\":1,\"name\":\"Alexandre Dumas\"},\"title\":\"The Three Musketeers\"},{\"id\":2,\"author\":{\"id\":2,\"name\":\"C.S. Lewis\"},\"title\":\"The Lion, the Witch and the Wardrobe\"},{\"id\":3,\"author\":{\"id\":3,\"name\":\"Robert C. Martin\"},\"title\":\"Clean <a href=\"https:\/\/thecodest.co\/sv\/dictionary\/what-is-code-refactoring\/\">Code<\/a>\"}]<\/code><\/pre>\n\n\n\n<p>The data seem to be fine, what about logs?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># request logs (n+1)\n\nStarted GET \"\/api\/v1\/books\" for 127.0.0.1 at 2021-12-24 10:19:40 +0100\nProcessing by API::V1::BooksController#index as *\/*\n  Book Load (0.1ms)  SELECT \"books\".* FROM \"books\"\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:7:in `index'\n  Author Load (0.1ms)  SELECT \"authors\".* FROM \"authors\" WHERE \"authors\".\"id\" = ? LIMIT ?  [[\"id\", 1], [\"LIMIT\", 1]]\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:7:in `index'\n  Author Load (0.1ms)  SELECT \"authors\".* FROM \"authors\" WHERE \"authors\".\"id\" = ? LIMIT ?  [[\"id\", 2], [\"LIMIT\", 1]]\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:7:in `index'\n  Author Load (0.1ms)  SELECT \"authors\".* FROM \"authors\" WHERE \"authors\".\"id\" = ? LIMIT ?  [[\"id\", 3], [\"LIMIT\", 1]]\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:7:in `index'\nCompleted 200 OK in 6ms (Views: 0.1ms | ActiveRecord: 0.4ms | Allocations: 3134)<\/code><\/pre>\n\n\n\n<p>By using association in our serializers we introduced <code>n+1<\/code> problem. We want to eliminate it by adding user a control on what he requests in this endpoint. So he should be able to either load only books, or pass the includes parameter and get authors as well, but preferably without the <code>n+1<\/code>.<\/p>\n\n\n\n<p>Let&#8217;s define a constant that will keep an information about what assocs of books user can include in <code>books#index<\/code> action:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># lib\/constants\/books\/includes.rb\n\nmodule Constants\n  module Books\n    module Includes\n      ALLOWED = {\n        index: %i[\n          author\n        ].freeze\n      }.freeze\n    end\n  end\nend<\/code><\/pre>\n\n\n\n<p>Next, we define a namespace for empty object constants:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># lib\/constants\/empty.rb\n\nmodule Constants\n  module Empty\n    <a href=\"https:\/\/thecodest.co\/sv\/blog\/hash-to-use-or-not-to-use\/\">HASH<\/a> = {}.freeze\n  end\nend<\/code><\/pre>\n\n\n\n<p>And here&#8217;s our main service for permitting includes. I think the code is pretty self-explanatory, some pieces of <code>magic<\/code> are only allocated in <code>#default_resources_key<\/code> and <code>#default_purpose<\/code>. These methods are defined to allow <a href=\"https:\/\/thecodest.co\/sv\/blog\/why-us-companies-are-opting-for-polish-developers\/\">us<\/a> to call permit includes passing only params in rails&#8217; controllers. The output will be the hash that stores <code>true<\/code> for each permitted inclusion.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/services\/permit_includes.rb\n\nrequire 'constants\/empty'\nrequire 'constants\/books\/includes'\n\nclass PermitIncludes\n  Empty = Constants::Empty\n\n  COMMA = ','\n  SLASH = '\/'\n\n  INCLUDES_FORMAT = \/A[a-z]+(,[a-z]+)*z\/.freeze\n  ALLOWED_INCLUDES = {\n    books: Constants::Books::Includes::ALLOWED\n  }.freeze\n\n  def call(params, resources: default_resources_key(params), purpose: default_purpose(params))\n    return Empty::HASH unless includes_sent?(params)\n    return Empty::HASH unless includes_valid?(params)\n\n    requested_includes = parse_includes(params)\n    allowed_includes = filter_includes(requested_includes, resources, purpose)\n\n    allowed_includes.index_with(true)\n  end\n\n  private\n\n  def default_resources_key(params)\n    raise(ArgumentError, 'params :controller key must be a string') unless params[:controller].is_a?(String)\n\n    params[:controller].split(SLASH).last&amp;.to_sym\n  end\n\n  def default_purpose(params)\n    raise(ArgumentError, 'params :action key must be a string') unless params[:action].is_a?(String)\n\n    params[:action].to_sym\n  end\n\n  def includes_sent?(params)\n    params.key?(:includes)\n  end\n\n  def includes_valid?(params)\n    return false unless params[:includes].is_a?(String)\n\n    params[:includes].match?(INCLUDES_FORMAT)\n  end\n\n  def parse_includes(params)\n    params[:includes].split(COMMA).map(&amp;:to_sym)\n  end\n\n  def filter_includes(requested_includes, resources_key, purpose)\n    requested_includes &amp; ALLOWED_INCLUDES[resources_key][purpose]\n  end\nend<\/code><\/pre>\n\n\n\n<p>Now we need to use the keys to load includes and pass the inlcudes hash itself to the serializer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/controllers\/api\/v1\/books_controller.rb\n\nmodule API\n  module V1\n    class BooksController &lt; BaseController\n      def index\n        includes = PermitIncludes.new.call(params)\n        books = Book.includes(includes.keys).all\n\n        render json: BookBlueprint.render(books, includes: includes)\n      end\n    end\n  end\nend<\/code><\/pre>\n\n\n\n<p>And this is how we must tweak our serializer &#8211; we load the association only if included:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"ruby\" class=\"language-ruby\"># app\/blueprints\/book_blueprint.rb\nclass BookBlueprint &lt; Blueprinter::Base\n  identifier :id\n\n  fields :title\n\n  association :author, blueprint: AuthorBlueprint,\n                       if: -&gt;(_field_name, _book, options) {\n                         options[:includes] &amp;&amp; options[:includes][:author]\n                       }\nend<\/code><\/pre>\n\n\n\n<p>Let&#8217;s test it again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">rails s<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">curl http:\/\/localhost:3000\/api\/v1\/books\n# => [{\"id\":1,\"title\":\"The Three Musketeers\"},{\"id\":2,\"title\":\"The Lion, the Witch and the Wardrobe\"},{\"id\":3,\"title\":\"Clean Code\"}]<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># request logs (we only load books)\nStarted GET \"\/api\/v1\/books\" for ::1 at 2021-12-24 10:33:41 +0100\nProcessing by API::V1::BooksController#index as *\/*\n   (0.1ms)  SELECT sqlite_version(*)\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:8:in `index'\n  Book Load (0.1ms)  SELECT \"books\".* FROM \"books\"\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:8:in `index'\nCompleted 200 OK in 9ms (Views: 0.1ms | ActiveRecord: 0.9ms | Allocations: 4548)<\/code><\/pre>\n\n\n\n<p>Good, we haven&#8217;t passed the includes so got only books, without authors. Let&#8217;s now request them:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">curl 'http:\/\/localhost:3000\/api\/v1\/books?includes=author'\n# => [{\"id\":1,\"author\":{\"id\":1,\"name\":\"Alexandre Dumas\"},\"title\":\"The Three Musketeers\"},{\"id\":2,\"author\":{\"id\":2,\"name\":\"C.S. Lewis\"},\"title\":\"The Lion, the Witch and the Wardrobe\"},{\"id\":3,\"author\":{\"id\":3,\"name\":\"Robert C. Martin\"},\"title\":\"Clean Code\"}]% <\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># request logs (eliminated n+1)\n\nStarted GET \"\/api\/v1\/books?includes=author\" for ::1 at 2021-12-24 10:38:23 +0100\nProcessing by API::V1::BooksController#index as *\/*\n  Parameters: {\"includes\"=>\"author\"}\n  Book Load (0.1ms)  SELECT \"books\".* FROM \"books\"\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:8:in `index'\n  Author Load (0.2ms)  SELECT \"authors\".* FROM \"authors\" WHERE \"authors\".\"id\" IN (?, ?, ?)  [[\"id\", 1], [\"id\", 2], [\"id\", 3]]\n  \u21b3 app\/controllers\/api\/v1\/books_controller.rb:8:in `index'\nCompleted 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 0.7ms | Allocations: 7373)<\/code><\/pre>\n\n\n\n<p>Cool! We got the association loaded and eliminated <code>n+1<\/code> problem. The service can be used for any resource, all we want to do is to add allowed inlcudes constants in the proper format and add them to <code>PermitIncludes::ALLOWED_INCLUDES<\/code>.<\/p>\n\n\n\n<p>We have to remember that this should be probably used with pagination (and caution) because including associations can &#8220;eat&#8221; a lot of memory.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Vi ska bygga en app f\u00f6r bokhyllor som listar b\u00f6cker med (eller utan) f\u00f6rfattardata.<\/p>","protected":false},"author":2,"featured_media":3374,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[8],"tags":[],"class_list":["post-3373","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-development"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Including Sub-resources in a REST-ish API - The Codest<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/thecodest.co\/sv\/blogg\/inkludera-underresurser-i-en-rest-ish-api\/\" \/>\n<meta property=\"og:locale\" content=\"sv_SE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Including Sub-resources in a REST-ish API\" \/>\n<meta property=\"og:description\" content=\"We will build a bookshelf app to list books with (or without) authors data.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/thecodest.co\/sv\/blogg\/inkludera-underresurser-i-en-rest-ish-api\/\" \/>\n<meta property=\"og:site_name\" content=\"The Codest\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-22T13:05:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-28T14:05:30+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png\" \/>\n\t<meta property=\"og:image:width\" content=\"960\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"thecodest\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"thecodest\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minuter\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/\"},\"author\":{\"name\":\"thecodest\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/#\\\/schema\\\/person\\\/7e3fe41dfa4f4e41a7baad4c6e0d4f76\"},\"headline\":\"Including Sub-resources in a REST-ish API\",\"datePublished\":\"2022-03-22T13:05:06+00:00\",\"dateModified\":\"2026-04-28T14:05:30+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/\"},\"wordCount\":503,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/05\\\/including_sub-resources_in_a_rest-ish_api.png\",\"articleSection\":[\"Software Development\"],\"inLanguage\":\"sv-SE\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/\",\"url\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/\",\"name\":\"Including Sub-resources in a REST-ish API - The Codest\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/05\\\/including_sub-resources_in_a_rest-ish_api.png\",\"datePublished\":\"2022-03-22T13:05:06+00:00\",\"dateModified\":\"2026-04-28T14:05:30+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#breadcrumb\"},\"inLanguage\":\"sv-SE\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"sv-SE\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#primaryimage\",\"url\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/05\\\/including_sub-resources_in_a_rest-ish_api.png\",\"contentUrl\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/05\\\/including_sub-resources_in_a_rest-ish_api.png\",\"width\":960,\"height\":540},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/blog\\\/including-sub-resources-in-a-rest-ish-api\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/thecodest.co\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Including Sub-resources in a REST-ish API\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/#website\",\"url\":\"https:\\\/\\\/thecodest.co\\\/\",\"name\":\"The Codest\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/thecodest.co\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"sv-SE\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/#organization\",\"name\":\"The Codest\",\"url\":\"https:\\\/\\\/thecodest.co\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"sv-SE\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/03\\\/thecodest-logo.svg\",\"contentUrl\":\"https:\\\/\\\/thecodest.co\\\/app\\\/uploads\\\/2024\\\/03\\\/thecodest-logo.svg\",\"width\":144,\"height\":36,\"caption\":\"The Codest\"},\"image\":{\"@id\":\"https:\\\/\\\/thecodest.co\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/pl.linkedin.com\\\/company\\\/codest\",\"https:\\\/\\\/clutch.co\\\/profile\\\/codest\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/thecodest.co\\\/#\\\/schema\\\/person\\\/7e3fe41dfa4f4e41a7baad4c6e0d4f76\",\"name\":\"thecodest\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"sv-SE\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g\",\"caption\":\"thecodest\"},\"url\":\"https:\\\/\\\/thecodest.co\\\/sv\\\/author\\\/thecodest\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Inkludera underresurser i ett REST-liknande API - The Codest","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/thecodest.co\/sv\/blogg\/inkludera-underresurser-i-en-rest-ish-api\/","og_locale":"sv_SE","og_type":"article","og_title":"Including Sub-resources in a REST-ish API","og_description":"We will build a bookshelf app to list books with (or without) authors data.","og_url":"https:\/\/thecodest.co\/sv\/blogg\/inkludera-underresurser-i-en-rest-ish-api\/","og_site_name":"The Codest","article_published_time":"2022-03-22T13:05:06+00:00","article_modified_time":"2026-04-28T14:05:30+00:00","og_image":[{"width":960,"height":540,"url":"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png","type":"image\/png"}],"author":"thecodest","twitter_card":"summary_large_image","twitter_misc":{"Written by":"thecodest","Est. reading time":"3 minuter"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#article","isPartOf":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/"},"author":{"name":"thecodest","@id":"https:\/\/thecodest.co\/#\/schema\/person\/7e3fe41dfa4f4e41a7baad4c6e0d4f76"},"headline":"Including Sub-resources in a REST-ish API","datePublished":"2022-03-22T13:05:06+00:00","dateModified":"2026-04-28T14:05:30+00:00","mainEntityOfPage":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/"},"wordCount":503,"commentCount":0,"publisher":{"@id":"https:\/\/thecodest.co\/#organization"},"image":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#primaryimage"},"thumbnailUrl":"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png","articleSection":["Software Development"],"inLanguage":"sv-SE","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/","url":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/","name":"Inkludera underresurser i ett REST-liknande API - The Codest","isPartOf":{"@id":"https:\/\/thecodest.co\/#website"},"primaryImageOfPage":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#primaryimage"},"image":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#primaryimage"},"thumbnailUrl":"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png","datePublished":"2022-03-22T13:05:06+00:00","dateModified":"2026-04-28T14:05:30+00:00","breadcrumb":{"@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#breadcrumb"},"inLanguage":"sv-SE","potentialAction":[{"@type":"ReadAction","target":["https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/"]}]},{"@type":"ImageObject","inLanguage":"sv-SE","@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#primaryimage","url":"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png","contentUrl":"https:\/\/thecodest.co\/app\/uploads\/2024\/05\/including_sub-resources_in_a_rest-ish_api.png","width":960,"height":540},{"@type":"BreadcrumbList","@id":"https:\/\/thecodest.co\/blog\/including-sub-resources-in-a-rest-ish-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/thecodest.co\/"},{"@type":"ListItem","position":2,"name":"Including Sub-resources in a REST-ish API"}]},{"@type":"WebSite","@id":"https:\/\/thecodest.co\/#website","url":"https:\/\/thecodest.co\/","name":"Codest","description":"","publisher":{"@id":"https:\/\/thecodest.co\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/thecodest.co\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"sv-SE"},{"@type":"Organization","@id":"https:\/\/thecodest.co\/#organization","name":"Codest","url":"https:\/\/thecodest.co\/","logo":{"@type":"ImageObject","inLanguage":"sv-SE","@id":"https:\/\/thecodest.co\/#\/schema\/logo\/image\/","url":"https:\/\/thecodest.co\/app\/uploads\/2024\/03\/thecodest-logo.svg","contentUrl":"https:\/\/thecodest.co\/app\/uploads\/2024\/03\/thecodest-logo.svg","width":144,"height":36,"caption":"The Codest"},"image":{"@id":"https:\/\/thecodest.co\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/pl.linkedin.com\/company\/codest","https:\/\/clutch.co\/profile\/codest"]},{"@type":"Person","@id":"https:\/\/thecodest.co\/#\/schema\/person\/7e3fe41dfa4f4e41a7baad4c6e0d4f76","name":"thecodest","image":{"@type":"ImageObject","inLanguage":"sv-SE","@id":"https:\/\/secure.gravatar.com\/avatar\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5dbfe6a1e8c86e432e8812759e34e6fe82ebac75119ae3237a6c1311fa19caf4?s=96&d=mm&r=g","caption":"thecodest"},"url":"https:\/\/thecodest.co\/sv\/author\/thecodest\/"}]}},"_links":{"self":[{"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/posts\/3373","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/comments?post=3373"}],"version-history":[{"count":14,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/posts\/3373\/revisions"}],"predecessor-version":[{"id":7886,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/posts\/3373\/revisions\/7886"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/media\/3374"}],"wp:attachment":[{"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/media?parent=3373"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/categories?post=3373"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thecodest.co\/sv\/wp-json\/wp\/v2\/tags?post=3373"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}