{"id":13516796,"url":"https://github.com/samsarahq/thunder","last_synced_at":"2025-09-27T07:31:31.853Z","repository":{"id":37692342,"uuid":"72047672","full_name":"samsarahq/thunder","owner":"samsarahq","description":"⚡️ A Go framework for rapidly building powerful graphql services","archived":true,"fork":false,"pushed_at":"2023-02-15T22:44:04.000Z","size":4632,"stargazers_count":1585,"open_issues_count":131,"forks_count":116,"subscribers_count":60,"default_branch":"master","last_synced_at":"2024-09-26T22:03:53.655Z","etag":null,"topics":["go","graphql","realtime","thunder"],"latest_commit_sha":null,"homepage":"https://godoc.org/github.com/samsarahq/thunder","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samsarahq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-10-26T21:54:28.000Z","updated_at":"2024-08-25T03:20:47.000Z","dependencies_parsed_at":"2023-02-16T23:00:31.940Z","dependency_job_id":null,"html_url":"https://github.com/samsarahq/thunder","commit_stats":{"total_commits":788,"total_committers":59,"mean_commits":13.35593220338983,"dds":0.8223350253807107,"last_synced_commit":"10681def87f58fd7928e245d2799c42fb1df583d"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samsarahq%2Fthunder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samsarahq%2Fthunder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samsarahq%2Fthunder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samsarahq%2Fthunder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samsarahq","download_url":"https://codeload.github.com/samsarahq/thunder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234410136,"owners_count":18828150,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","graphql","realtime","thunder"],"created_at":"2024-08-01T05:01:25.959Z","updated_at":"2025-09-27T07:31:30.082Z","avatar_url":"https://github.com/samsarahq.png","language":"JavaScript","funding_links":[],"categories":["Misc","开源类库","JavaScript","Open source library","Repositories"],"sub_categories":["查询语言","Query Language"],"readme":"# ⚠️ Deprecated\n\nAs of Feb 15, 2023, this repository is deprecated and no longer maintained. If you are looking for a GraphQL server library in Golang, please consider [alternatives](https://graphql.org/code/#go). Thank you for your interest in Thunder.\n\n# Thunder\n\nThunder is a Go framework for rapidly building powerful graphql servers.\nThunder has support for schemas automatically generated from Go types, live\nqueries, query batching, and more. Thunder is an open-source project from\nSamsara.\n\n[![Documentation](https://godoc.org/github.com/samsarahq/thunder?status.svg)](http://godoc.org/github.com/samsarahq/thunder)\n\n# Feature Lightning Tour\n\nThunder has a number of features to make it easy to build sophisticated\nschemas. This section provides a brief overview of some of them.\n\n## Reflection-based schema building\n\nThunder generates resolvers automatically from Go struct types and function\ndefinitions. For example, the `Friend` struct below gets exposed as a graphql\nobject type with `firstName` and `lastName` resolvers that return the fields\non the type.\n\n```go\n// Friend is a small struct representing a person.\ntype Friend struct {\n  FirstName string\n  Last string `graphql:\"lastName\"` // use a custom name\n\n  Added time.Date `graphql:\"-\"` // don't expose over graphql\n}\n\n// FullName builds a friend's full name.\nfunc (f *Friend) FullName() string {\n  return fmt.Sprintf(\"%s %s\", f.FirstName, f.Last)\n}\n\n// registerFriend registers custom resolvers on the Friend type.\n//\n// Note: registerFriend wouldn't be necessary if the type only\n// had the default struct field resolvers above.\nfunc registerFriend(schema *schemabuilder.Schema) {\n  object := schema.Object(\"Friend\", Friend{})\n\n  // fullName is a computed field on the Friend{} object.\n  object.FieldFunc(\"fullName\", Friend.FullName)\n}\n```\n\n## [Pagination](./doc/pagination.md)\n\n## Live queries\n\nThunder has support for automatically updating queries using _resolver\ninvalidation_. With invalidation, code on the server can trigger updates on\nthe client using a persistent WebSocket connection.\n\nThe simplest example is a clock that updates over time. Every 10 seconds the\n`time` function will be recomputed, and the latest time will be sent to the\nclient.\n\n```go\n// registerQuery registers the resolvers on the core graphql query type.\nfunc registerQuery(schema *schemabuilder.Schema) {\n  query := schema.Query()\n\n  // time returns the current time.\n  query.FieldFunc(\"time\", func(ctx context.Context) string {\n    // Invalidate the result of this resolver after 10 seconds.\n    reactive.InvalidateAfter(ctx, 10 * time.Second)\n    // Return the current time. Will be re-executed automatically.\n    return time.Now().String()\n  })\n}\n```\n\nUsing Thunder's lightweight `sqlgen` and `livesql` ORM, it's easy to write\nautomatically updating MySQL queries. The example below returns a live-updating\nlists of posts from a database table. Whenever somebody `INSERT`s or `UPDATE`s\na row in the table, the resolver is re-executed and the latest lists of posts\nis sent to the client. Behind the scenes, the `livesql` package uses MySQL's\nbinary replication log to detect changes to the underlying data.\n\n```go\n// A Post holds a row from the MySQL posts table.\ntype Post struct {\n  Id    int64 `sqlgen:\",primary\"`\n  Title string\n}\n\n// Server implements a graphql server. It has persistent handles to eg. the\n// database.\ntype Server struct {\n  db *livesql.LiveDB\n}\n\n// registerQuery registers the root query resolvers.\nfunc (s *Server) registerQuery(schema *schemabuilder.Schema) {\n  query := schema.Query()\n  // posts returns all posts in the database.\n  query.FieldFunc(\"posts\", func(ctx context.Context) ([]*Post, error) {\n    var posts []*Post\n    if err := s.db.Query(ctx, \u0026posts, nil, nil); err != nil {\n      return nil, err\n    }\n    return posts, nil\n  })\n}\n```\n\n## Built-in parallel execution and batching\n\nThunder automatically runs independent resolvers in different goroutines to\nquickly compute complex queries. To keep large queries efficient, Thunder has\nsupport for built-in batching similar to Facebook's `dataloader`. With\nbatching, Thunder automatically combines many parallel individual calls to a\n`batch.Func`'s `Invoke` function into a single call to `Many` function.\n\nBatching is very useful when fetching related objects from a SQL database. Thunder's\n`sqlgen` and `livesql` have built-in support for batching and will combine `SELECT WHERE`\nstatements using an `IN` clause. For example, the program below will fetch all posts and\ntheir authors in just two queries.\n\n```go\ntype Post struct {\n  Id    int64 `sqlgen:\",primary\"`\n  Title string\n  AuthorId int64\n}\n\n// An Author represents a row in the authors table.\ntype Author struct {\n  Id   int64 `sqlgen:\",primary\"`\n  Name string\n}\n\n// registerPost registers resolvers on the Post type.\nfunc (s *Server) registerPost(schema *schemabuilder.Schema) {\n  object := schema.Object(\"post\", Post{})\n  // author return the Author object corresponding to a Post's AuthorId.\n  object.FieldFunc(\"author\", func(ctx context.Context, p *Post) (*Author, error) {\n    var author *Author\n    if err := s.db.QueryRow(ctx, \u0026author, sqlgen.Filter{\"id\": p.AuthorId}, nil); err != nil {\n      return nil, err\n    }\n    return author, nil\n  })\n}\n```\n\nTo execute the query\n```graphql\nquery PostsWithAuthors {\n  posts {\n    title\n    author { name }\n  }\n}\n```\nThunder will execute `SELECT * FROM posts` and, if that returns three posts\nwith author IDs `10`, `20`, and `31`, a follow-up query `SELECT * FROM\nauthors WHERE id IN (10, 20, 31)`.\n\n## Built-in graphiql\n\nTo get started quickly without wrangling any JavaScript, Thunder comes with\na built-in `graphiql` client as an HTTP handler. To use it, simply expose\nwith Go's built-in HTTP server.\n\n```go\n// Expose schema and graphiql.\nhttp.Handle(\"/graphql\", graphql.Handler(schema))\nhttp.Handle(\"/graphiql/\", http.StripPrefix(\"/graphiql/\", graphiql.Handler()))\nhttp.ListenAndServe(\":3030\", nil)\n```\n\n## Split schema building for large graphql servers\n\nA large GraphQL server might have many resolvers on some shared types. To\nkeep packages reasonably-sized, Thunder's schema builder supports extending a\nschema. For example, if you have a `User` type with a resolver `photos`\nimplemented by your `photos` package, and resolver `events` implemented by\nyour `calendar` package, those packages can independently register their\nresolvers:\n\n```go\npackage common\n\ntype User struct {}\n\n\npackage photos\n\ntype PhotosServer {}\n\nfunc (s *PhotosServer) registerUser(schema *schemabuilder.Schema) {\n  object := schema.Object(\"User\", common.User{})\n  object.FieldFunc(\"photos\", s.fetchUserPhotos)\n}\n\n\npackage events\n\ntype EventsServer {}\n\nfunc (s *EventsServer) registerUser(schema *schemabuilder.Schema) {\n  object := schema.Object(\"User\", common.User{})\n  object.FieldFunc(\"events\", s.fetchUserEvents)\n}\n```\n\n# Getting started\n\n\u003e First, a fair warning. The Thunder library is still a little bit tricky to use\n\u003e outside of Samsara. The examples above and below work, but eg. the `npm` client\n\u003e still requires some wrangling.\n\n## A minimal complete server\n\nThe program below is a fully-functional graphql server written using Thunder. It\ndoes not use `sqlgen`, `livesql`, or batching, but does include a live-updating\nresolver.\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/samsarahq/thunder/graphql\"\n  \"github.com/samsarahq/thunder/graphql/graphiql\"\n  \"github.com/samsarahq/thunder/graphql/introspection\"\n  \"github.com/samsarahq/thunder/graphql/schemabuilder\"\n  \"github.com/samsarahq/thunder/reactive\"\n)\n\ntype post struct {\n  Title     string\n  Body      string\n  CreatedAt time.Time\n}\n\n// server is our graphql server.\ntype server struct {\n  posts []post\n}\n\n// registerQuery registers the root query type.\nfunc (s *server) registerQuery(schema *schemabuilder.Schema) {\n  obj := schema.Query()\n\n  obj.FieldFunc(\"posts\", func() []post {\n    return s.posts\n  })\n}\n\n// registerMutation registers the root mutation type.\nfunc (s *server) registerMutation(schema *schemabuilder.Schema) {\n  obj := schema.Mutation()\n  obj.FieldFunc(\"echo\", func(args struct{ Message string }) string {\n    return args.Message\n  })\n}\n\n// registerPost registers the post type.\nfunc (s *server) registerPost(schema *schemabuilder.Schema) {\n  obj := schema.Object(\"Post\", post{})\n  obj.FieldFunc(\"age\", func(ctx context.Context, p *post) string {\n    reactive.InvalidateAfter(ctx, 5*time.Second)\n    return time.Since(p.CreatedAt).String()\n  })\n}\n\n// schema builds the graphql schema.\nfunc (s *server) schema() *graphql.Schema {\n  builder := schemabuilder.NewSchema()\n  s.registerQuery(builder)\n  s.registerMutation(builder)\n  s.registerPost(builder)\n  return builder.MustBuild()\n}\n\nfunc main() {\n  // Instantiate a server, build a server, and serve the schema on port 3030.\n  server := \u0026server{\n    posts: []post{\n      {Title: \"first post!\", Body: \"I was here first!\", CreatedAt: time.Now()},\n      {Title: \"graphql\", Body: \"did you hear about Thunder?\", CreatedAt: time.Now()},\n    },\n  }\n\n  schema := server.schema()\n  introspection.AddIntrospectionToSchema(schema)\n\n  // Expose schema and graphiql.\n  http.Handle(\"/graphql\", graphql.Handler(schema))\n  http.Handle(\"/graphiql/\", http.StripPrefix(\"/graphiql/\", graphiql.Handler()))\n  http.ListenAndServe(\":3030\", nil)\n}\n```\n\n## Using Thunder without Websockets (POST requests)\n\nFor use with non-live clients (e.g. [Relay](https://facebook.github.io/relay/), [Apollo](https://www.apollographql.com/client/)) thunder provides an HTTP handler that can serve\nPOST requests, instead of having the client connect over a websocket. In this mode, thunder\ndoes not provide live query updates.\n\nIn the above example, the `main` function would be changed to look like:\n\n```go\nfunc main() {\n  // Instantiate a server, build a server, and serve the schema on port 3030.\n  server := \u0026server{\n    posts: []post{\n      {Title: \"first post!\", Body: \"I was here first!\", CreatedAt: time.Now()},\n      {Title: \"graphql\", Body: \"did you hear about Thunder?\", CreatedAt: time.Now()},\n    },\n  }\n\n  schema := server.schema()\n  introspection.AddIntrospectionToSchema(schema)\n\n  // Expose GraphQL POST endpoint.\n  http.Handle(\"/graphql\", graphql.HTTPHandler(schema))\n  http.ListenAndServe(\":3030\", nil)\n}\n```\n\n## Emitting a schema.json\n\nThunder can emit a GraphQL introspection query schema useful for compatibility with\nother GraphQL tooling. Alongside code from the above example, here is a small program\nfor registering our schema and writing the JSON output to stdout.\n\n```go\n// schema_generator.go\n\nfunc main() {\n  // Instantiate a server and run the introspection query on it.\n  server := \u0026server{...}\n\n  builderSchema := schemabuilder.NewSchema()\n  server.registerQuery(builderSchema)\n  server.registerMutation(builderSchema)\n  // ...\n\n  valueJSON, err := introspection.ComputeSchemaJSON(*builderSchema)\n  if err != nil {\n    panic(err)\n  }\n\n  fmt.Print(string(valueJSON))\n}\n```\n\nThis program can then be run to generate `schema.json`:\n```bash\n$ go run schema_generator.go \u003e schema.json\n```\n\n## Code organization\n\nThe source code in this repository is organized as follows:\n- The example/ directory contains a basic Thunder application.\n- The graphql/ directory contains Thunder's graphql parser and executor.\n- The reactive/ directory contains Thunder's core dependency-tracking and\n  live-update mechanism.\n- The batch/ directory contains Thunder's batching package.\n- The diff/ and merge/ directories contain Thunder's JSON diffing library\n  used for live queries.\n- The livesql/ directory contains a Thunder driver for MySQL.\n- The sqlgen/ directory contains a lightweight SQL query generator used by\n  livesql/.\n\n# Status\n\n Thunder has proven itself in production use at Samsara for close to two\n years. This repository is still under development, and there will be some\n breaking changes to the API but they should be manageable. If you're\n adventurous, please give it a try.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamsarahq%2Fthunder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamsarahq%2Fthunder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamsarahq%2Fthunder/lists"}