github.com/99designs/gqlgen@v0.17.45/docs/content/recipes/federation.md (about) 1 --- 2 title: 'Using Apollo federation gqlgen' 3 description: How federate many services into a single graph using Apollo 4 linkTitle: Apollo Federation 5 menu: { main: { parent: 'recipes' } } 6 --- 7 8 In this quick guide we are going to implement the example [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) 9 server in gqlgen. You can find the finished result in the [examples directory](https://github.com/99designs/gqlgen/tree/master/_examples/federation). 10 11 ## Enable federation 12 13 Uncomment federation configuration in your `gqlgen.yml` 14 15 ```yml 16 # Uncomment to enable federation 17 federation: 18 filename: graph/federation.go 19 package: graph 20 ``` 21 22 ### Federation 2 23 24 If you are using Apollo's Federation 2 standard, your schema should automatically be upgraded so long as you include the required `@link` directive within your schema. If you want to force Federation 2 composition, the `federation` configuration supports a `version` flag to override that. For example: 25 26 ```yml 27 federation: 28 filename: graph/federation.go 29 package: graph 30 version: 2 31 ``` 32 33 ## Create the federated servers 34 35 For each server to be federated we will create a new gqlgen project. 36 37 ```bash 38 go run github.com/99designs/gqlgen 39 ``` 40 41 Update the schema to reflect the federated example 42 ```graphql 43 type Review { 44 body: String 45 author: User @provides(fields: "username") 46 product: Product 47 } 48 49 extend type User @key(fields: "id") { 50 id: ID! @external # External directive not required for key fields in federation v2 51 reviews: [Review] 52 } 53 54 extend type Product @key(fields: "upc") { 55 upc: String! @external # External directive not required for key fields in federation v2 56 reviews: [Review] 57 } 58 ``` 59 60 61 and regenerate 62 ```bash 63 go run github.com/99designs/gqlgen 64 ``` 65 66 then implement the resolvers 67 ```go 68 // These two methods are required for gqlgen to resolve the internal id-only wrapper structs. 69 // This boilerplate might be removed in a future version of gqlgen that can no-op id only nodes. 70 func (r *entityResolver) FindProductByUpc(ctx context.Context, upc string) (*model.Product, error) { 71 return &model.Product{ 72 Upc: upc, 73 }, nil 74 } 75 76 func (r *entityResolver) FindUserByID(ctx context.Context, id string) (*model.User, error) { 77 return &model.User{ 78 ID: id, 79 }, nil 80 } 81 82 // Here we implement the stitched part of this service, returning reviews for a product. Of course normally you would 83 // go back to the database, but we are just making some data up here. 84 func (r *productResolver) Reviews(ctx context.Context, obj *model.Product) ([]*model.Review, error) { 85 switch obj.Upc { 86 case "top-1": 87 return []*model.Review{{ 88 Body: "A highly effective form of birth control.", 89 }}, nil 90 91 case "top-2": 92 return []*model.Review{{ 93 Body: "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.", 94 }}, nil 95 96 case "top-3": 97 return []*model.Review{{ 98 Body: "This is the last straw. Hat you will wear. 11/10", 99 }}, nil 100 101 } 102 return nil, nil 103 } 104 105 func (r *userResolver) Reviews(ctx context.Context, obj *model.User) ([]*model.Review, error) { 106 if obj.ID == "1234" { 107 return []*model.Review{{ 108 Body: "Has an odd fascination with hats.", 109 }}, nil 110 } 111 return nil, nil 112 } 113 ``` 114 115 > Note 116 > 117 > Repeat this step for each of the services in the apollo doc (accounts, products, reviews) 118 119 ## Create the federation gateway 120 121 ```bash 122 npm install --save @apollo/gateway apollo-server graphql 123 ``` 124 125 ```typescript 126 const { ApolloServer } = require('apollo-server'); 127 const { ApolloGateway } = require("@apollo/gateway"); 128 129 const gateway = new ApolloGateway({ 130 serviceList: [ 131 { name: 'accounts', url: 'http://localhost:4001/query' }, 132 { name: 'products', url: 'http://localhost:4002/query' }, 133 { name: 'reviews', url: 'http://localhost:4003/query' } 134 ], 135 }); 136 137 const server = new ApolloServer({ 138 gateway, 139 140 subscriptions: false, 141 }); 142 143 server.listen().then(({ url }) => { 144 console.log(`🚀 Server ready at ${url}`); 145 }); 146 ``` 147 148 ## Start all the services 149 150 In separate terminals: 151 ```bash 152 go run accounts/server.go 153 go run products/server.go 154 go run reviews/server.go 155 node gateway/index.js 156 ``` 157 158 ## Query the federated gateway 159 160 The examples from the apollo doc should all work, eg 161 162 ```graphql 163 query { 164 me { 165 username 166 reviews { 167 body 168 product { 169 name 170 upc 171 } 172 } 173 } 174 } 175 ``` 176 177 should return 178 179 ```json 180 { 181 "data": { 182 "me": { 183 "username": "Me", 184 "reviews": [ 185 { 186 "body": "A highly effective form of birth control.", 187 "product": { 188 "name": "Trilby", 189 "upc": "top-1" 190 } 191 }, 192 { 193 "body": "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.", 194 "product": { 195 "name": "Trilby", 196 "upc": "top-1" 197 } 198 } 199 ] 200 } 201 } 202 } 203 ``` 204 205 ## Explicit `@requires` Directive 206 If you need to support **nested** or **array** fields in the `@requires` directive, this can be enabled in the configuration by setting `federation.options.explicit_requires` to true. 207 208 ```yml 209 federation: 210 filename: graph/federation.go 211 package: graph 212 version: 2 213 options: 214 explicit_requires: true 215 ``` 216 217 Enabling this will generate corresponding functions with the entity representations received in the request. This allows for the entity model to be explicitly populated with the required data provided. 218 219 ### Example 220 Take a simple todo app schema that needs to provide a formatted status text to be used across all clients by referencing the assignee's name. 221 222 ```graphql 223 type Todo @key(fields:"id") { 224 id: ID! 225 text: String! 226 statusText: String! @requires(fields: "assignee { name }") 227 status: String! 228 owner: User! 229 assignee: User! 230 } 231 232 type User @key(fields:"id") { 233 id: ID! 234 name: String! @external 235 } 236 ``` 237 238 A `PopulateTodoRequires` function is generated, and can be modified accordingly to use the todo representation with the assignee name. 239 240 ```golang 241 // PopulateTodoRequires is the requires populator for the Todo entity. 242 func (ec *executionContext) PopulateTodoRequires(ctx context.Context, entity *model.Todo, reps map[string]interface{}) error { 243 if reps["assignee"] != nil { 244 entity.StatusText = entity.Status + " by " + reps["assignee"].(map[string]interface{})["name"].(string) 245 } 246 return nil 247 } 248 ```