|
1 | 1 | --- |
2 | 2 | title: API |
3 | 3 | description: >- |
4 | | - This page covers adding new Spree API endpoints and customization of |
5 | | - existing ones |
| 4 | + Add new Store API endpoints and customize existing API responses |
6 | 5 | --- |
7 | 6 |
|
8 | 7 | Before you start customizing Spree API endpoints, make sure you reviewed all existing API endpoints in the [Spree API docs](/api-reference). |
9 | 8 |
|
10 | | -## Customizing JSON response |
| 9 | +For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [Store API tutorial](/developer/tutorial/store-api). |
11 | 10 |
|
12 | | -Spree uses a library called [JSON API serializers](https://github.com/jsonapi-serializer/jsonapi-serializer) to represent data returned by API endpoints. |
| 11 | +## Customizing JSON Responses |
13 | 12 |
|
14 | | -You can easily replace existing Spree serializers with your own thanks to [Spree Dependencies](dependencies). Here's a list of all serializers that you can override: |
| 13 | +Spree uses [Alba](https://github.com/okuramasafumi/alba) serializers to build API v3 responses. You can replace any serializer via [Spree Dependencies](dependencies). |
15 | 14 |
|
16 | | -| Key | Value | |
17 | | -|------------------------------------------|-----------------------------------------------------| |
18 | | -| `storefront_address_serializer` | [Spree::V2::Storefront::AddressSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/address_serializer.rb) | |
19 | | -| `storefront_cart_serializer` | [Spree::V2::Storefront::CartSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/cart_serializer.rb) | |
20 | | -| `storefront_credit_card_serializer` | [Spree::V2::Storefront::CreditCardSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/credit_card_serializer.rb) | |
21 | | -| `storefront_country_serializer` | [Spree::V2::Storefront::CountrySerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/country_serializer.rb) | |
22 | | -| `storefront_user_serializer` | [Spree::V2::Storefront::UserSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/user_serializer.rb) | |
23 | | -| `storefront_shipment_serializer` | [Spree::V2::Storefront::ShipmentSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/shipment_serializer.rb) | |
24 | | -| `storefront_taxon_serializer` | [Spree::V2::Storefront::TaxonSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/taxon_serializer.rb) | |
25 | | -| `storefront_payment_method_serializer` | [Spree::V2::Storefront::PaymentMethodSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/payment_method_serializer.rb) | |
26 | | -| `storefront_payment_serializer` | [Spree::V2::Storefront::PaymentSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/payment_serializer.rb) | |
27 | | -| `storefront_product_serializer` | [Spree::V2::Storefront::ProductSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/product_serializer.rb) | |
28 | | -| `storefront_estimated_shipment_serializer` | [Spree::V2::Storefront::EstimatedShippingRateSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/estimated_shipping_rate_serializer.rb) | |
29 | | -| `storefront_store_serializer` | [Spree::V2::Storefront::StoreSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/store_serializer.rb) | |
30 | | -| `storefront_order_serializer` | [Spree::V2::Storefront::OrderSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/order_serializer.rb) | |
31 | | -| `storefront_variant_serializer` | [Spree::V2::Storefront::VariantSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/variant_serializer.rb) | |
| 15 | +### Adding Custom Attributes |
32 | 16 |
|
33 | | -### Adding custom attributes |
| 17 | +Let's say you want to add a `my_custom_attribute` column to the Product API response. |
34 | 18 |
|
35 | | -<Note> |
36 | | -As a rule of thumb it's recommended to use [Properties](/developer/core-concepts/products#product-properties) and [OptionTypes/Option Values](/developer/core-concepts/products#option-types-and-option-values) for custom attributes and not to modify the Spree database schema |
37 | | -</Note> |
| 19 | +Create a custom serializer that inherits from the core one: |
38 | 20 |
|
39 | | -Let's say you want to customize the Storefront API's [Product serializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/product_serializer.rb) to include you custom database column `my_newcustom_attribute` that you've added to the `spree_products` database table. |
| 21 | +```ruby app/serializers/my_app/product_serializer.rb |
| 22 | +module MyApp |
| 23 | + class ProductSerializer < Spree::Api::V3::ProductSerializer |
| 24 | + attribute :my_custom_attribute |
| 25 | + end |
| 26 | +end |
| 27 | +``` |
40 | 28 |
|
41 | | -Let's start with creating a new serializer file: |
| 29 | +Register it in `config/initializers/spree.rb`: |
42 | 30 |
|
43 | | -```bash |
44 | | -mkdir -p app/serializers && touch app/serializers/my_product_serializer.rb |
| 31 | +```ruby |
| 32 | +Spree::Api::Dependencies.product_serializer = 'MyApp::ProductSerializer' |
45 | 33 | ``` |
46 | 34 |
|
47 | | -Place the following code in your new serializer file: |
| 35 | +Restart the server and the Product API will include your new attribute. |
48 | 36 |
|
49 | | -```ruby |
50 | | -class MyProductSerializer < Spree::V2::Storefront::ProductSerializer |
51 | | - attribute :my_new_custom_attribute |
| 37 | +### Adding an Association |
| 38 | + |
| 39 | +Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](/developer/tutorial/store-api) for the full example). |
| 40 | + |
| 41 | +Create a serializer for the new model: |
| 42 | + |
| 43 | +```ruby app/serializers/spree/api/v3/brand_serializer.rb |
| 44 | +module Spree |
| 45 | + module Api |
| 46 | + module V3 |
| 47 | + class BrandSerializer < BaseSerializer |
| 48 | + typelize name: :string, slug: [:string, nullable: true] |
| 49 | + |
| 50 | + attributes :name, :slug |
| 51 | + end |
| 52 | + end |
| 53 | + end |
52 | 54 | end |
53 | 55 | ``` |
54 | 56 |
|
55 | | -<Info> |
56 | | -This serializer will inherit from the `Spree::V2::Storefront::ProductSerializer` serializer, so you don't need to rewrite the whole serializer. |
57 | | -</Info> |
| 57 | +Then subclass the Product serializer to include the brand association: |
58 | 58 |
|
59 | | -Now let's tell Spree to use this new serializer, in `config/initializers/spree.rb` please set: |
| 59 | +```ruby app/serializers/my_app/product_serializer.rb |
| 60 | +module MyApp |
| 61 | + class ProductSerializer < Spree::Api::V3::ProductSerializer |
| 62 | + typelize brand_id: [:string, nullable: true] |
60 | 63 |
|
61 | | -```ruby |
62 | | -Spree::Api::Dependencies.storefront_product_serializer = 'MyProductSerializer' |
| 64 | + attribute :brand_id do |product| |
| 65 | + product.brand&.prefixed_id |
| 66 | + end |
| 67 | + |
| 68 | + one :brand, |
| 69 | + resource: Spree::Api::V3::BrandSerializer, |
| 70 | + if: proc { expand?('brand') } |
| 71 | + end |
| 72 | +end |
63 | 73 | ``` |
64 | 74 |
|
65 | | -Restart the webserver and hit the [Products API](/api-reference/storefront/products/list-all-products) to notice that the payload now includes your new attribute. |
| 75 | +Register it in `config/initializers/spree.rb`: |
66 | 76 |
|
67 | | -### Adding a new association |
| 77 | +```ruby |
| 78 | +Spree::Api::Dependencies.product_serializer = 'MyApp::ProductSerializer' |
| 79 | +``` |
68 | 80 |
|
69 | | -Let's say you've created a new model called `Video` that belongs to `Product` (_Product has multiple Videos_). |
| 81 | +The brand data is available via `?expand=brand`: |
70 | 82 |
|
71 | | -Let's create a new serializer `app/serializers/video_serializer.rb`: |
| 83 | +``` |
| 84 | +GET /api/v3/store/products/prod_86Rf07xd4z?expand=brand |
| 85 | +``` |
72 | 86 |
|
73 | | -```ruby |
74 | | -class VideoSerializer < Spree::Api::V2::BaseSerializer |
75 | | - set_type: :video |
76 | | - |
77 | | - attributes :url |
| 87 | +### Serializer Dependency Keys |
| 88 | + |
| 89 | +Here are the most commonly customized v3 serializers: |
| 90 | + |
| 91 | +| Key | Default | |
| 92 | +|-----|---------| |
| 93 | +| `product_serializer` | `Spree::Api::V3::ProductSerializer` | |
| 94 | +| `variant_serializer` | `Spree::Api::V3::VariantSerializer` | |
| 95 | +| `cart_serializer` | `Spree::Api::V3::CartSerializer` | |
| 96 | +| `order_serializer` | `Spree::Api::V3::OrderSerializer` | |
| 97 | +| `line_item_serializer` | `Spree::Api::V3::LineItemSerializer` | |
| 98 | +| `category_serializer` | `Spree::Api::V3::CategorySerializer` | |
| 99 | +| `customer_serializer` | `Spree::Api::V3::CustomerSerializer` | |
| 100 | +| `address_serializer` | `Spree::Api::V3::AddressSerializer` | |
| 101 | +| `payment_serializer` | `Spree::Api::V3::PaymentSerializer` | |
| 102 | +| `fulfillment_serializer` | `Spree::Api::V3::FulfillmentSerializer` | |
| 103 | + |
| 104 | +See `Spree::Api::ApiDependencies` for the full list. |
| 105 | + |
| 106 | +## Adding New Endpoints |
| 107 | + |
| 108 | +### Controller |
| 109 | + |
| 110 | +Inherit from `Spree::Api::V3::Store::ResourceController` for Store API endpoints: |
| 111 | + |
| 112 | +```ruby app/controllers/spree/api/v3/store/brands_controller.rb |
| 113 | +module Spree |
| 114 | + module Api |
| 115 | + module V3 |
| 116 | + module Store |
| 117 | + class BrandsController < ResourceController |
| 118 | + protected |
| 119 | + |
| 120 | + def model_class |
| 121 | + Spree::Brand |
| 122 | + end |
| 123 | + |
| 124 | + def serializer_class |
| 125 | + Spree::Api::V3::BrandSerializer |
| 126 | + end |
| 127 | + |
| 128 | + def scope |
| 129 | + Spree::Brand.all |
| 130 | + end |
| 131 | + end |
| 132 | + end |
| 133 | + end |
| 134 | + end |
78 | 135 | end |
79 | 136 | ``` |
80 | 137 |
|
81 | | -Now in your `app/serializers/my_product_serializer.rb` |
| 138 | +The base `ResourceController` provides: |
| 139 | + |
| 140 | +| Feature | How | |
| 141 | +|---------|-----| |
| 142 | +| Pagination | [Pagy](https://github.com/ddnexus/pagy) — `?page=2&limit=25` | |
| 143 | +| Filtering | [Ransack](https://github.com/activerecord-hackery/ransack) — `?q[name_cont]=nike` | |
| 144 | +| Sorting | JSON:API style — `?sort=-name` | |
| 145 | +| Authorization | [CanCanCan](https://github.com/CanCanCommunity/cancancan) | |
| 146 | +| Prefixed IDs | Stripe-style — `brand_k5nR8xLq` | |
| 147 | + |
| 148 | +Override these methods to customize: |
| 149 | + |
| 150 | +| Method | Purpose | |
| 151 | +|--------|---------| |
| 152 | +| `model_class` | ActiveRecord model (required) | |
| 153 | +| `serializer_class` | Alba serializer (required) | |
| 154 | +| `scope` | Base query — add `.where()` to filter | |
| 155 | +| `find_resource` | Custom ID lookup (slug fallback, etc.) | |
| 156 | +| `permitted_params` | Custom strong params | |
| 157 | +| `collection_includes` | Eager loading for `index` | |
| 158 | + |
| 159 | +### Routes |
| 160 | + |
| 161 | +Add routes in your app's `config/routes.rb`: |
82 | 162 |
|
83 | 163 | ```ruby |
84 | | -class MyProductSerializer < Spree::V2::Storefront::ProductSerializer |
85 | | - attribute :my_new_custom_attribute |
86 | | - |
87 | | - has_many :videos, serializer: :video |
| 164 | +Spree::Core::Engine.add_routes do |
| 165 | + namespace :api, defaults: { format: 'json' } do |
| 166 | + namespace :v3 do |
| 167 | + namespace :store do |
| 168 | + resources :brands, only: [:index, :show] |
| 169 | + end |
| 170 | + end |
| 171 | + end |
88 | 172 | end |
89 | 173 | ``` |
90 | 174 |
|
91 | | -Hitting the Products API you will notice that in the [relationships](https://jsonapi.org/format/#document-resource-object-relationships) key there will be a new key called `videos` |
| 175 | +### Permitted Attributes |
92 | 176 |
|
93 | | -To include Video response in the Product API add `?includes=videos` in the API URL, eg. |
| 177 | +For endpoints that accept writes (create/update), add your attributes: |
94 | 178 |
|
95 | | -```http |
96 | | -GET https://localhost:3000/api/v2/storefront/products?include=videos |
| 179 | +```ruby config/initializers/spree.rb |
| 180 | +Spree::PermittedAttributes.brand_attributes = [:name, :slug, :description, :logo] |
97 | 181 | ``` |
| 182 | + |
| 183 | +Or override `permitted_params` in the controller: |
| 184 | + |
| 185 | +```ruby |
| 186 | +def permitted_params |
| 187 | + params.permit(:name, :slug, :description, :logo) |
| 188 | +end |
| 189 | +``` |
| 190 | + |
| 191 | +## Consuming Custom Endpoints with the SDK |
| 192 | + |
| 193 | +See the [SDK tutorial](/developer/tutorial/sdk) for how to call custom endpoints from TypeScript and extend the generated types. |
0 commit comments