1# A Facebook Graph API SDK In Golang #
2
3[![Build Status](https://travis-ci.org/huandu/facebook.svg?branch=master)](https://travis-ci.org/huandu/facebook)
4[![GoDoc](https://godoc.org/github.com/huandu/facebook?status.svg)](https://godoc.org/github.com/huandu/facebook)
5
6This is a Go package that fully supports the [Facebook Graph API](https://developers.facebook.com/docs/graph-api/) with file upload, batch request and marketing API. It can be used in Google App Engine.
7
8API documentation can be found on [godoc](http://godoc.org/github.com/huandu/facebook).
9
10Feel free to create an issue or send me a pull request if you have any "how-to" question or bug or suggestion when using this package. I'll try my best to reply it.
11
12## Get It ##
13
14Use `go get -u github.com/huandu/facebook` to get or update it.
15
16## Usage ##
17
18### Quick start ###
19
20Here is a sample that reads my Facebook first name by uid.
21
22```go
23package main
24
25import (
26    "fmt"
27    fb "github.com/huandu/facebook"
28)
29
30func main() {
31    res, _ := fb.Get("/538744468", fb.Params{
32        "fields": "first_name",
33        "access_token": "a-valid-access-token",
34    })
35    fmt.Println("Here is my Facebook first name:", res["first_name"])
36}
37```
38
39The type of `res` is `fb.Result` (a.k.a. `map[string]interface{}`).
40This type has several useful methods to decode `res` to any Go type safely.
41
42```go
43// Decode "first_name" to a Go string.
44var first_name string
45res.DecodeField("first_name", &first_name)
46fmt.Println("Here's an alternative way to get first_name:", first_name)
47
48// It's also possible to decode the whole result into a predefined struct.
49type User struct {
50    FirstName string
51}
52
53var user User
54res.Decode(&user)
55fmt.Println("print first_name in struct:", user.FirstName)
56```
57
58If a type implements the `json.Unmarshaler` interface, `Decode` or `DecodeField` will use it to unmarshal JSON.
59
60```go
61res := Result{
62    "create_time": "2006-01-02T15:16:17Z",
63}
64
65// Type `*time.Time` implements `json.Unmarshaler`.
66// res.DecodeField will use the interface to unmarshal data.
67var tm time.Time
68res.DecodeField("create_time", &tm)
69```
70
71### Read a graph `user` object with a valid access token ###
72
73```go
74res, err := fb.Get("/me/feed", fb.Params{
75     "access_token": "a-valid-access-token",
76})
77
78if err != nil {
79    // err can be a Facebook API error.
80    // if so, the Error struct contains error details.
81    if e, ok := err.(*Error); ok {
82        fmt.Printf("facebook error. [message:%v] [type:%v] [code:%v] [subcode:%v] [trace:%v]",
83            e.Message, e.Type, e.Code, e.ErrorSubcode, e.TraceID)
84        return
85    }
86
87    return
88}
89
90// read my last feed story.
91fmt.Println("My latest feed story is:", res.Get("data.0.story"))
92```
93
94### Read a graph `search` for page and decode slice of maps
95
96```go
97res, _ := fb.Get("/search", fb.Params{
98        "access_token": "a-valid-access-token",
99        "type":         "page",
100        "q":            "nightlife,singapore",
101    })
102
103var items []fb.Result
104
105err := res.DecodeField("data", &items)
106
107if err != nil {
108    fmt.Printf("An error has happened %v", err)
109    return
110}
111
112for _, item := range items {
113    fmt.Println(item["id"])
114}
115```
116
117### Use `App` and `Session` ###
118
119It's recommended to use `App` and `Session` in a production app. They provide more control over all API calls. They can also make code clearer and more concise.
120
121```go
122// Create a global App var to hold app id and secret.
123var globalApp = fb.New("your-app-id", "your-app-secret")
124
125// Facebook asks for a valid redirect uri when parsing signed request.
126// It's a new enforced policy starting as of late 2013.
127globalApp.RedirectUri = "http://your.site/canvas/url/"
128
129// Here comes a client with a Facebook signed request string in query string.
130// This will return a new session from a signed request.
131session, _ := globalApp.SessionFromSignedRequest(signedRequest)
132
133// If there is another way to get decoded access token,
134// this will return a session created directly from the token.
135session := globalApp.Session(token)
136
137// This validates the access token by ensuring that the current user ID is properly returned. err is nil if token is valid.
138err := session.Validate()
139
140// Use the new session to send an API request with access token.
141res, _ := session.Get("/me/feed", nil)
142```
143
144By default all requests are sent to Facebook servers. If you wish to override API base URL for unit-testing purposes - just set respective `Session` field:
145```go
146testSrv := httptest.NewServer(someMux)
147session.BaseURL = testSrv.URL + "/"
148```
149
150Facebook returns most timestamps in a ISO9601 format which can't be natively parsed by Go's `encoding/json`.
151Setting `RFC3339Timestamps` `true` on the `Session` or at the global level will cause proper RFC3339 timestamps to be requested from Facebook.
152RFC3339 is what `encoding/json` natively expects.
153
154```go
155fb.RFC3339Timestamps = true
156session.RFC3339Timestamps = true
157```
158
159Setting either of these to true will cause `date_format=Y-m-d\TH:i:sP` to be sent as a parameter on every request. The format string is a PHP `date()` representation of RFC3339.
160More info is available in [this issue](https://github.com/huandu/facebook/issues/95).
161
162### Use `paging` field in response. ###
163
164Some Graph API responses use a special JSON structure to provide paging information. Use `Result.Paging()` to walk through all data in such results.
165
166```go
167res, _ := session.Get("/me/home", nil)
168
169// create a paging structure.
170paging, _ := res.Paging(session)
171
172var allResults []Result
173
174// append first page of results to slice of Result
175allResults = append(allResults, paging.Data()...)
176
177for {
178  // get next page.
179  noMore, err := paging.Next()
180  if err != nil {
181    panic(err)
182  }
183  if noMore {
184    // No more results available
185    break
186  }
187  // append current page of results to slice of Result
188  allResults = append(allResults, paging.Data()...)
189}
190
191```
192
193### Read Graph API response and decode result into a struct ###
194
195The Facebook Graph API always uses snake case keys in API response.
196This package can automatically convert from snake case to Go's camel-case-style style struct field names.
197
198For instance, to decode following JSON response...
199
200```json
201{
202    "foo_bar": "player"
203}
204```
205
206One can use following struct.
207
208```go
209type Data struct {
210    FooBar string  // "FooBar" maps to "foo_bar" in JSON automatically in this case.
211}
212```
213
214The decoding of each struct field can be customized by the format string stored under the `facebook` key or the "json" key in the struct field's tag. The `facebook` key is recommended as it's specifically designed for this package.
215
216Following is a sample shows all possible field tags.
217
218```go
219// define a Facebook feed object.
220type FacebookFeed struct {
221    Id          string            `facebook:",required"`             // this field must exist in response.
222                                                                     // mind the "," before "required".
223    Story       string
224    FeedFrom    *FacebookFeedFrom `facebook:"from"`                  // use customized field name "from".
225    CreatedTime string            `facebook:"created_time,required"` // both customized field name and "required" flag.
226    Omitted     string            `facebook:"-"`                     // this field is omitted when decoding.
227}
228
229type FacebookFeedFrom struct {
230    Name string `json:"name"`                   // the "json" key also works as expected.
231    Id string   `facebook:"id" json:"shadowed"` // if both "facebook" and "json" key are set, the "facebook" key is used.
232}
233
234// create a feed object direct from Graph API result.
235var feed FacebookFeed
236res, _ := session.Get("/me/feed", nil)
237res.DecodeField("data.0", &feed) // read latest feed
238```
239
240### Send a batch request ###
241
242```go
243params1 := Params{
244    "method": fb.GET,
245    "relative_url": "me",
246}
247params2 := Params{
248    "method": fb.GET,
249    "relative_url": uint64(100002828925788),
250}
251results, err := fb.BatchApi(your_access_token, params1, params2)
252
253if err != nil {
254    // check error...
255    return
256}
257
258// batchResult1 and batchResult2 are response for params1 and params2.
259batchResult1, _ := results[0].Batch()
260batchResult2, _ := results[1].Batch()
261
262// Use parsed result.
263var id string
264res := batchResult1.Result
265res.DecodeField("id", &id)
266
267// Use response header.
268contentType := batchResult1.Header.Get("Content-Type")
269```
270
271### Using with Google App Engine ###
272
273Google App Engine provides the `appengine/urlfetch` package as the standard HTTP client package.
274For this reason, the default client in `net/http` won't work.
275One must explicitly set the HTTP client in `Session` to make it work.
276
277```go
278import (
279    "appengine"
280    "appengine/urlfetch"
281)
282
283// suppose it's the appengine context initialized somewhere.
284var context appengine.Context
285
286// default Session object uses http.DefaultClient which is not allowed to use
287// in appengine. one has to create a Session and assign it a special client.
288seesion := globalApp.Session("a-access-token")
289session.HttpClient = urlfetch.Client(context)
290
291// now, session uses appengine http client now.
292res, err := session.Get("/me", nil)
293```
294
295### Select Graph API version ###
296
297See [Platform Versioning](https://developers.facebook.com/docs/apps/versions) to understand Facebook versioning strategy.
298
299```go
300// this package uses default version which is controlled by Facebook app setting.
301// change following global variable to specific a global default version.
302fb.Version = "v3.0"
303
304// starting with Graph API v2.0; it's not allowed to get user information without access token.
305fb.Api("huan.du", GET, nil)
306
307// it's possible to specify version per session.
308session := &fb.Session{}
309session.Version = "v3.0" // overwrite global default.
310```
311
312### Enable `appsecret_proof` ###
313
314Facebook can verify Graph API Calls with `appsecret_proof`. It's a feature to make Graph API call more secure. See [Securing Graph API Requests](https://developers.facebook.com/docs/graph-api/securing-requests) to know more about it.
315
316```go
317globalApp := fb.New("your-app-id", "your-app-secret")
318
319// enable "appsecret_proof" for all sessions created by this app.
320globalApp.EnableAppsecretProof = true
321
322// all calls in this session are secured.
323session := globalApp.Session("a-valid-access-token")
324session.Get("/me", nil)
325
326// it's also possible to enable/disable this feature per session.
327session.EnableAppsecretProof(false)
328```
329
330### Debugging API Requests ###
331
332Facebook has introduced a way to debug Graph API calls. See [Debugging API Requests](https://developers.facebook.com/docs/graph-api/using-graph-api/debugging) for more details.
333
334This package provides both a package level and per session debug flag. Set `Debug` to a `DEBUG_*` constant to change debug mode globally; or use `Session#SetDebug` to change debug mode for one session.
335
336When debug mode is turned on, use `Result#DebugInfo` to get `DebugInfo` struct from the result.
337
338```go
339fb.Debug = fb.DEBUG_ALL
340
341res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"})
342debugInfo := res.DebugInfo()
343
344fmt.Println("http headers:", debugInfo.Header)
345fmt.Println("facebook api version:", debugInfo.FacebookApiVersion)
346```
347
348### Monitoring API usage info ###
349
350Call `Result#UsageInfo` to get a `UsageInfo` struct containing both app and page level rate limit information from the result. More information about rate limiting can be found [here](https://developers.facebook.com/docs/graph-api/advanced/rate-limiting).
351
352```go
353res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"})
354usageInfo := res.UsageInfo()
355
356fmt.Println("App level rate limit information:", usageInfo.App)
357fmt.Println("Page level rate limit information:", usageInfo.Page)
358```
359
360### Work with package `golang.org/x/oauth2` ##
361
362The `golang.org/x/oauth2` package can handle the Facebook OAuth2 authentication process and access token quite well. This package can work with it by setting `Session#HttpClient` to OAuth2's client.
363
364```go
365import (
366    "golang.org/x/oauth2"
367    oauth2fb "golang.org/x/oauth2/facebook"
368    fb "github.com/huandu/facebook"
369)
370
371// Get Facebook access token.
372conf := &oauth2.Config{
373    ClientID:     "AppId",
374    ClientSecret: "AppSecret",
375    RedirectURL:  "CallbackURL",
376    Scopes:       []string{"email"},
377    Endpoint:     oauth2fb.Endpoint,
378}
379token, err := conf.Exchange(oauth2.NoContext, "code")
380
381// Create a client to manage access token life cycle.
382client := conf.Client(oauth2.NoContext, token)
383
384// Use OAuth2 client with session.
385session := &fb.Session{
386    Version:    "v2.4",
387    HttpClient: client,
388}
389
390// Use session.
391res, _ := session.Get("/me", nil)
392```
393
394### Control timeout and cancelation with `Context` ###
395
396The `Session` accept a `Context`.
397
398```go
399// Create a new context.
400ctx, cancel := context.WithTimeout(session.Context(), 100 * time.Millisecond)
401defer cancel()
402
403// Call an API with ctx.
404// The return value of `session.WithContext` is a shadow copy of original session and
405// should not be stored. It can be used only once.
406result, err := session.WithContext(ctx).Get("/me", nil)
407```
408
409See [this Go blog post about context](https://blog.golang.org/context) for more details about how to use `Context`.
410
411## Change Log ##
412
413See [CHANGELOG.md](CHANGELOG.md).
414
415## Out of Scope ##
416
4171. No OAuth integration. This package only provides APIs to parse/verify access token and code generated in OAuth 2.0 authentication process.
4182. No old RESTful API and FQL support. Such APIs are deprecated for years. Forget about them.
419
420## License ##
421
422This package is licensed under the MIT license. See LICENSE for details.
423