From 3411ae3a8a864df86ff030c046e1a19532882cd3 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Wed, 24 Jul 2024 12:45:27 +0100 Subject: [PATCH 1/9] feat(config): add full JsonPlaceholder configuration --- src/components/home/Configuration.tsx | 610 +++++++++++++++++++++----- 1 file changed, 507 insertions(+), 103 deletions(-) diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index e41b8237ac..fce32b9625 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -21,16 +21,16 @@ const Configuration = (): JSX.Element => { npm i -g @tailcallhq/tailcall - {CodeTabItem({code: GRAPHQL_CONFIG, language: "graphql"})} - {CodeTabItem({code: YML_CONFIG, language: "yaml"})} - {CodeTabItem({code: JSON_CONFIG, language: "json"})} + {CodeTabItem({ code: GRAPHQL_CONFIG, language: "graphql" })} + {CodeTabItem({ code: YML_CONFIG, language: "yaml" })} + {CodeTabItem({ code: JSON_CONFIG, language: "json" })} ) } -const CodeTabItem = ({code, language}: {code: string; language: "json" | "yaml" | "graphql"}) => ( +const CodeTabItem = ({ code, language }: { code: string; language: "json" | "yaml" | "graphql" }) => ( Date: Wed, 24 Jul 2024 12:52:14 +0100 Subject: [PATCH 2/9] fix: prettier fix --- src/components/home/Configuration.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index fce32b9625..26c01c2fe9 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -21,16 +21,16 @@ const Configuration = (): JSX.Element => { npm i -g @tailcallhq/tailcall - {CodeTabItem({ code: GRAPHQL_CONFIG, language: "graphql" })} - {CodeTabItem({ code: YML_CONFIG, language: "yaml" })} - {CodeTabItem({ code: JSON_CONFIG, language: "json" })} + {CodeTabItem({code: GRAPHQL_CONFIG, language: "graphql"})} + {CodeTabItem({code: YML_CONFIG, language: "yaml"})} + {CodeTabItem({code: JSON_CONFIG, language: "json"})} ) } -const CodeTabItem = ({ code, language }: { code: string; language: "json" | "yaml" | "graphql" }) => ( +const CodeTabItem = ({code, language}: {code: string; language: "json" | "yaml" | "graphql"}) => ( Date: Wed, 24 Jul 2024 23:45:56 +0100 Subject: [PATCH 3/9] fix: json config validation fix --- src/components/home/Configuration.tsx | 220 +++++++++++++------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index 26c01c2fe9..afc5a27ad2 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -387,9 +387,7 @@ types: const JSON_CONFIG = ` { - "server": { - "port": 8000 - }, + "server": {}, "upstream": { "baseURL": "https://jsonplaceholder.typicode.com" }, @@ -399,92 +397,73 @@ const JSON_CONFIG = ` "types": { "Address": { "fields": { - "city": {"type": "String"}, - "geo": {"type": "Geo"}, - "street": {"type": "String"}, - "suite": {"type": "String"}, - "zipcode": {"type": "String"} + "city": { "type": "String" }, + "geo": { "type": "Geo" }, + "street": { "type": "String" }, + "suite": { "type": "String" }, + "zipcode": { "type": "String" } } }, "Company": { "fields": { - "bs": {"type": "String"}, - "catchPhrase": {"type": "String"}, - "name": {"type": "String"} + "bs": { "type": "String" }, + "catchPhrase": { "type": "String" }, + "name": { "type": "String" } } }, "Geo": { "fields": { - "lat": {"type": "String"}, - "lng": {"type": "String"} + "lat": { "type": "String" }, + "lng": { "type": "String" } } }, "Photo": { "fields": { - "albumId": {"type": "Int"}, - "id": {"type": "Int"}, - "thumbnailUrl": {"type": "String"}, - "title": {"type": "String"}, - "url": {"type": "String"}, + "albumId": { "type": "Int" }, + "id": { "type": "Int" }, + "thumbnailUrl": { "type": "String" }, + "title": { "type": "String" }, + "url": { "type": "String" }, "album": { "type": "Album", "call": { - "steps": [ - { - "query": "album", - "args": {"id": "{{.value.albumId}}"} - } - ] + "steps": [{ "query": "album", "args": { "id": "{{.value.albumId}}" } }] } } } }, "Post": { "fields": { - "body": {"type": "String"}, - "id": {"type": "Int"}, - "title": {"type": "String"}, - "userId": {"type": "Int"}, + "body": { "type": "String" }, + "id": { "type": "Int" }, + "title": { "type": "String" }, + "userId": { "type": "Int" }, "user": { "type": "User", "call": { - "steps": [ - { - "query": "user", - "args": {"id": "{{.value.userId}}"} - } - ] + "steps": [{ "query": "user", "args": { "id": "{{.value.userId}}" } }] } }, "comments": { "type": "Comment", "list": true, "call": { - "steps": [ - { - "query": "comments" - } - ] + "steps": [{ "query": "comments", "args": { "postId": "{{.value.id}}" } }] } } } }, "Comment": { "fields": { - "body": {"type": "String"}, - "email": {"type": "String"}, - "id": {"type": "Int"}, - "name": {"type": "String"}, - "postId": {"type": "Int"}, + "body": { "type": "String" }, + "email": { "type": "String" }, + "id": { "type": "Int" }, + "name": { "type": "String" }, + "postId": { "type": "Int" }, "post": { "type": "Post", "call": { - "steps": [ - { - "query": "post", - "args": {"id": "{{.value.postId}}"} - } - ] + "steps": [{ "query": "post", "args": { "id": "{{.value.postId}}" } }] } } } @@ -493,144 +472,165 @@ const JSON_CONFIG = ` "fields": { "album": { "type": "Album", - "args": {"id": "Int!"}, - "http": {"path": "/albums/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/albums/{{.args.id}}" + } }, "albums": { "type": "Album", "list": true, - "http": {"path": "/albums"} + "http": { + "path": "/albums" + } }, "comment": { "type": "Comment", - "args": {"id": "Int!"}, - "http": {"path": "/comments/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/comments/{{.args.id}}" + } }, "comments": { "type": "Comment", "list": true, - "http": {"path": "/comments"} + "http": { + "path": "/comments" + } }, "photo": { "type": "Photo", - "args": {"id": "Int!"}, - "http": {"path": "/photos/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/photos/{{.args.id}}" + } }, "photos": { "type": "Photo", "list": true, - "http": {"path": "/photos"} + "http": { + "path": "/photos" + } }, "post": { "type": "Post", - "args": {"id": "Int!"}, - "http": {"path": "/posts/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/posts/{{.args.id}}" + } }, "posts": { "type": "Post", "list": true, - "http": {"path": "/posts"} + "http": { + "path": "/posts" + } }, "todo": { "type": "Todo", - "args": {"id": "Int!"}, - "http": {"path": "/todos/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/todos/{{.args.id}}" + } }, "todos": { "type": "Todo", "list": true, - "http": {"path": "/todos"} + "http": { + "path": "/todos" + } }, "user": { "type": "User", - "args": {"id": "Int!"}, - "http": {"path": "/users/{{.args.id}}"} + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/users/{{.args.id}}" + } }, "users": { "type": "User", "list": true, - "http": {"path": "/users"} + "http": { + "path": "/users" + } } } }, "Todo": { "fields": { - "completed": {"type": "Boolean"}, - "id": {"type": "Int"}, - "title": {"type": "String"}, - "userId": {"type": "Int"}, + "completed": { "type": "Boolean" }, + "id": { "type": "Int" }, + "title": { "type": "String" }, + "userId": { "type": "Int" }, "user": { "type": "User", "call": { - "steps": [ - { - "query": "user", - "args": {"id": "{{.value.userId}}"} - } - ] + "steps": [{ "query": "user", "args": { "id": "{{.value.userId}}" } }] } } } }, "User": { "fields": { - "address": {"type": "Address"}, - "company": {"type": "Company"}, - "email": {"type": "String"}, - "id": {"type": "Int"}, - "name": {"type": "String"}, - "phone": {"type": "String"}, - "username": {"type": "String"}, - "website": {"type": "String"}, + "address": { "type": "Address" }, + "company": { "type": "Company" }, + "email": { "type": "String" }, + "id": { "type": "Int" }, + "name": { "type": "String" }, + "phone": { "type": "String" }, + "username": { "type": "String" }, + "website": { "type": "String" }, "posts": { "type": "Post", "list": true, "call": { - "steps": [ - { - "query": "posts" - } - ] + "steps": [{ "query": "posts", "args": { "userId": "{{.value.id}}" } }] } }, "todos": { "type": "Todo", "list": true, "call": { - "steps": [ - { - "query": "todos" - } - ] + "steps": [{ "query": "todos", "args": { "userId": "{{.value.id}}" } }] + } + }, + "albums": { + "type": "Album", + "list": true, + "call": { + "steps": [{ "query": "albums", "args": { "userId": "{{.value.id}}" } }] } } } }, "Album": { "fields": { - "id": {"type": "Int"}, - "title": {"type": "String"}, - "userId": {"type": "Int"}, + "id": { "type": "Int" }, + "title": { "type": "String" }, + "userId": { "type": "Int" }, "user": { "type": "User", "call": { - "steps": [ - { - "query": "user", - "args": {"id": "{{.value.userId}}"} - } - ] + "steps": [{ "query": "user", "args": { "id": "{{.value.userId}}" } }] } }, "photos": { "type": "Photo", "list": true, "call": { - "steps": [ - { - "query": "photos" - } - ] + "steps": [{ "query": "photos", "args": { "albumId": "{{.value.id}}" } }] } } } From fe11b30ba1ae54c6fc7ae80726aaa5456d56334f Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Tue, 30 Jul 2024 18:02:44 +0100 Subject: [PATCH 4/9] fix: expanded call operator and moved config to getting started guide --- docs/getting-started.mdx | 1514 ++++++++++++++++++++++++- src/components/home/Configuration.tsx | 607 ++-------- 2 files changed, 1550 insertions(+), 571 deletions(-) diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 70f09631c0..3224cc5250 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -130,50 +130,291 @@ The command will ask you a few questions and based on your input bootstrap a new ## Writing a GraphQL Configuration -For our first example, we are going to compose a GraphQL schema from the REST APIs at https://jsonplaceholder.typicode.com, a free online REST API with some fake data. -We will use the API at `/users` to get a list of users, and `/users/:id/posts` to get the posts for each user, and compose them into a single GraphQL schema. +For our first example, we are going to compose a GraphQL schema from the REST APIs at https://jsonplaceholder.typicode.com, a free online REST API with some fake data. +We will use multiple endpoints to gather various types of data, such as users, posts, comments, albums, photos, and todos. By doing so, we will compose them into a single, unified GraphQL schema. +This approach allows us to fetch and manipulate data in a more structured and flexible way compared to traditional REST APIs. We can use the following formats to define our GraphQL schema: `.graphql`, `.yml`, `.json`. Create one of the following files and paste the contents into it. + ```graphql showLineNumbers -schema +# Define the schema with upstream configuration and server settings +schema # Specify server configuration: Start GraphQL server at 0.0.0.0:8000 @server(port: 8000) - # Specify a base url for all http requests - - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + # Specify a base URL for all HTTP requests + @upstream(baseURL: "https://jsonplaceholder.typicode.com") { query: Query + mutation: Mutation } +# Define the Query type with various fields type Query { - # Specify the http path for the users query + # Fetch a post by ID + post(id: Int!): Post @http(path: "/posts/{{.args.id}}") + # Fetch all posts by a specific user + postsFromUser(userId: Int!): [Post] @http(path: "/posts?userId={{.args.userId}}") + # Fetch a comment by ID + comment(id: Int!): Comment @http(path: "/comments/{{.args.id}}") + # Fetch all comments on a specific post + commentsFromPost(postId: Int!): [Comment] @http(path: "/comments?postId={{.args.postId}}") + # Fetch an album by ID + album(id: Int!): Album @http(path: "/albums/{{.args.id}}") + # Fetch all albums by a specific user + albumsFromUser(userId: Int!): [Album] @http(path: "/albums?userId={{.args.userId}}") + # Fetch a photo by ID + photo(id: Int!): Photo @http(path: "/photos/{{.args.id}}") + # Fetch all photos in a specific album + photosFromAlbum(albumId: Int!): [Photo] @http(path: "/photos?albumId={{.args.albumId}}") + # Fetch a todo by ID + todo(id: Int!): Todo @http(path: "/todos/{{.args.id}}") + # Fetch all todos by a specific user + todosFromUser(userId: Int!): [Todo] @http(path: "/todos?userId={{.args.userId}}") + # Fetch a user by ID + user(id: Int!): User @http(path: "/users/{{.args.id}}") + # Fetch all users users: [User] @http(path: "/users") } -# Create a user type with the fields returned by the users api +# Define the Mutation type with various fields for creating, updating, and deleting resources +type Mutation { + # Create a new post + createPost(input: PostInput!): Post @http(method: "POST", path: "/posts", body: "{{.args.input}}") + # Update an existing post + updatePost(id: Int!, input: PostInput!): Post @http(method: "PUT", path: "/posts/{{.args.id}}", body: "{{.args.input}}") + # Patch (partially update) an existing post + patchPost(id: Int!, input: PartialPostInput!): Post @http(method: "PATCH", path: "/posts/{{.args.id}}", body: "{{.args.input}}") + # Delete a post + deletePost(id: Int!): Boolean @http(method: "DELETE", path: "/posts/{{.args.id}}") + + # Create a new comment + createComment(input: CommentInput!): Comment @http(method: "POST", path: "/comments", body: "{{.args.input}}") + # Update an existing comment + updateComment(id: Int!, input: CommentInput!): Comment @http(method: "PUT", path: "/comments/{{.args.id}}", body: "{{.args.input}}") + # Delete a comment + deleteComment(id: Int!): Boolean @http(method: "DELETE", path: "/comments/{{.args.id}}") + + # Create a new album + createAlbum(input: AlbumInput!): Album @http(method: "POST", path: "/albums", body: "{{.args.input}}") + # Update an exixting album + updateAlbum(id: Int!, input: AlbumInput!): Album @http(method: "PUT", path: "/albums/{{.args.id}}", body: "{{.args.input}}") + # Delete an album + deleteAlbum(id: Int!): Boolean @http(method: "DELETE", path: "/albums/{{.args.id}}") + + # Create a new photo + createPhoto(input: PhotoInput!): Photo @http(method: "POST", path: "/photos", body: "{{.args.input}}") + # Update an existing photo + updatePhoto(id: Int!, input: PhotoInput!): Photo @http(method: "PUT", path: "/photos/{{.args.id}}", body: "{{.args.input}}") + # Delete a photo + deletePhoto(id: Int!): Boolean @http(method: "DELETE", path: "/photos/{{.args.id}}") + + # Create a new todo + createTodo(input: TodoInput!): Todo @http(method: "POST", path: "/todos", body: "{{.args.input}}") + # Update an existing todo + updateTodo(id: Int!, input: TodoInput!): Todo @http(method: "PUT", path: "/todos/{{.args.id}}", body: "{{.args.input}}") + # Delete a todo + deleteTodo(id: Int!): Boolean @http(method: "DELETE", path: "/todos/{{.args.id}}") + + # Create a new user + createUser(input: UserInput!): User @http(method: "POST", path: "/users", body: "{{.args.input}}") + # Update an existing user + updateUser(id: Int!, input: UserInput!): User @http(method: "PUT", path: "/users/{{.args.id}}", body: "{{.args.input}}") + # Delete a user + deleteUser(id: Int!): Boolean @http(method: "DELETE", path: "/users/{{.args.id}}") +} + +# Define the Post type with its fields and related queries +type Post { + id: Int! + userId: Int! + title: String! + body: String! + # Fetch the user who created the post + user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) + # Fetch all comments on the post + comments: [Comment] @call(steps: [{query: "commentsFromPost", args: {postId: "{{.value.id}}"}}]) +} + +# Define the Comment type with its fields and related queries +type Comment { + id: Int! + postId: Int! + name: String! + email: String! + body: String! + # Fetch the post to which the comment belongs + post: Post @call(steps: [{query: "post", args: {id: "{{.value.postId}}"}}]) +} +# Define the Album type with its fields and related queries +type Album { + id: Int! + userId: Int! + title: String! + # Fetch the user who created the album + user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) + # Fetch all photos in the album + photos: [Photo] @call(steps: [{query: "photosFromAlbum", args: {albumId: "{{.value.id}}"}}]) +} + +# Define the Photo type with its fields and related queries +type Photo { + id: Int! + albumId: Int! + title: String! + url: String! + thumbnailUrl: String! + # Fetch the album to which the photo belongs + album: Album @call(steps: [{query: "album", args: {id: "{{.value.albumId}}"}}]) +} + +# Define the Todo type with its fields and related queries +type Todo { + id: Int! + userId: Int! + title: String! + completed: Boolean! + # Fetch the user who created the todo + user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) +} + +# Define the User type with its fields and related queries type User { id: Int! name: String! username: String! email: String! + address: Address + phone: String + website: String + company: Company + # Fetch all posts by the user + posts: [Post] @call(steps: [{query: "postsFromUser", args: {userId: "{{.value.id}}"}}]) + # Fetch all albums by the user + albums: [Album] @call(steps: [{query: "albumsFromUser", args: {userId: "{{.value.id}}"}}]) + # Fetch all todos by the user + todos: [Todo] @call(steps: [{query: "todosFromUser", args: {userId: "{{.value.id}}"}}]) +} - # Extend the user type with the posts field - # Use the current user's id to construct the path - posts: [Post] @http(path: "/users/{{.value.id}}/posts") +# Define the Address type with its fields +type Address { + street: String + suite: String + city: String + zipcode: String + geo: Geo } -# Create a post type with the fields returned by the posts api -type Post { - id: Int! +# Define the Geo type with its fields +type Geo { + lat: String + lng: String +} + +# Define the Company type with its fields +type Company { + name: String + catchPhrase: String + bs: String +} + +# Define the input type for creating a post +input PostInput { + userId: Int! title: String! body: String! } + +# Define the input type for partially updating a post +input PartialPostInput { + userId: Int + title: String + body: String +} + +# Define the input type for creating a comment +input CommentInput { + postId: Int! + name: String! + email: String! + body: String! +} + +# Define the input type for creating an album +input AlbumInput { + userId: Int! + title: String! +} + +# Define the input type for creating a photo +input PhotoInput { + albumId: Int! + title: String! + url: String! + thumbnailUrl: String! +} + +# Define the input type for creating a todo +input TodoInput { + userId: Int! + title: String! + completed: Boolean! +} + +# Define the input type for creating a user +input UserInput { + name: String! + username: String! + email: String! + address: AddressInput + phone: String + website: String + company: CompanyInput +} + +# Define the input type for the address field +input AddressInput { + street: String + suite: String + city: String + zipcode: String + geo: GeoInput +} + +# Define the input type for the geo field +input GeoInput { + lat: String + lng: String +} + +# Define the input type for the company field +input CompanyInput { + name: String + catchPhrase: String + bs: String +} + +# Define a complex query type for fetching user data along with posts and comments +type ComplexQuery { + userWithPostsAndComments(userId: Int!): UserWithPostsAndComments + @call(steps: [ + {query: "user", args: {id: "{{.args.userId}}"}}, + {query: "postsFromUser", args: {userId: "{{.value.id}}"}}, + {query: "commentsFromPost", args: {postId: "{{.value.posts.0.id}}"}} + ]) +} + +# Define the UserWithPostsAndComments type with fields for user, posts, and comments +type UserWithPostsAndComments { + user: User + posts: [Post] + comments: [Comment] +} ``` @@ -185,54 +426,655 @@ server: queryValidation: false hostname: "0.0.0.0" upstream: - baseURL: "http://jsonplaceholder.typicode.com" + baseURL: https://jsonplaceholder.typicode.com httpCache: 42 schema: query: Query + mutation: Mutation types: + Query: + fields: + post: + type: Post + args: + id: + type: Int + required: true + http: + path: /posts/{{.args.id}} + postsFromUser: + type: Post + list: true + args: + userId: + type: Int + required: true + http: + path: /posts?userId={{.args.userId}} + comment: + type: Comment + args: + id: + type: Int + required: true + http: + path: /comments/{{.args.id}} + commentsFromPost: + type: Comment + list: true + args: + postId: + type: Int + required: true + http: + path: /comments?postId={{.args.postId}} + album: + type: Album + args: + id: + type: Int + required: true + http: + path: /albums/{{.args.id}} + albumsFromUser: + type: Album + list: true + args: + userId: + type: Int + required: true + http: + path: /albums?userId={{.args.userId}} + photo: + type: Photo + args: + id: + type: Int + required: true + http: + path: /photos/{{.args.id}} + photosFromAlbum: + type: Photo + list: true + args: + albumId: + type: Int + required: true + http: + path: /photos?albumId={{.args.albumId}} + todo: + type: Todo + args: + id: + type: Int + required: true + http: + path: /todos/{{.args.id}} + todosFromUser: + type: Todo + list: true + args: + userId: + type: Int + required: true + http: + path: /todos?userId={{.args.userId}} + user: + type: User + args: + id: + type: Int + required: true + http: + path: /users/{{.args.id}} + users: + type: User + list: true + http: + path: /users + Mutation: + fields: + createPost: + type: Post + args: + input: + type: PostInput + required: true + http: + method: POST + path: /posts + body: "{{.args.input}}" + updatePost: + type: Post + args: + id: + type: Int + required: true + input: + type: PostInput + required: true + http: + method: PUT + path: /posts/{{.args.id}} + body: "{{.args.input}}" + patchPost: + type: Post + args: + id: + type: Int + required: true + input: + type: PartialPostInput + required: true + http: + method: PATCH + path: /posts/{{.args.id}} + body: "{{.args.input}}" + deletePost: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /posts/{{.args.id}} + createComment: + type: Comment + args: + input: + type: CommentInput + required: true + http: + method: POST + path: /comments + body: "{{.args.input}}" + updateComment: + type: Comment + args: + id: + type: Int + required: true + input: + type: CommentInput + required: true + http: + method: PUT + path: /comments/{{.args.id}} + body: "{{.args.input}}" + deleteComment: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /comments/{{.args.id}} + createAlbum: + type: Album + args: + input: + type: AlbumInput + required: true + http: + method: POST + path: /albums + body: "{{.args.input}}" + updateAlbum: + type: Album + args: + id: + type: Int + required: true + input: + type: AlbumInput + required: true + http: + method: PUT + path: /albums/{{.args.id}} + body: "{{.args.input}}" + deleteAlbum: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /albums/{{.args.id}} + createPhoto: + type: Photo + args: + input: + type: PhotoInput + required: true + http: + method: POST + path: /photos + body: "{{.args.input}}" + updatePhoto: + type: Photo + args: + id: + type: Int + required: true + input: + type: PhotoInput + required: true + http: + method: PUT + path: /photos/{{.args.id}} + body: "{{.args.input}}" + deletePhoto: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /photos/{{.args.id}} + createTodo: + type: Todo + args: + input: + type: TodoInput + required: true + http: + method: POST + path: /todos + body: "{{.args.input}}" + updateTodo: + type: Todo + args: + id: + type: Int + required: true + input: + type: TodoInput + required: true + http: + method: PUT + path: /todos/{{.args.id}} + body: "{{.args.input}}" + deleteTodo: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /todos/{{.args.id}} + createUser: + type: User + args: + input: + type: UserInput + required: true + http: + method: POST + path: /users + body: "{{.args.input}}" + updateUser: + type: User + args: + id: + type: Int + required: true + input: + type: UserInput + required: true + http: + method: PUT + path: /users/{{.args.id}} + body: "{{.args.input}}" + deleteUser: + type: Boolean + args: + id: + type: Int + required: true + http: + method: DELETE + path: /users/{{.args.id}} Post: fields: + id: + type: Int + required: true + userId: + type: Int + required: true + title: + type: String + required: true + body: + type: String + required: true + user: + type: User + call: + steps: + - query: user + args: + id: "{{.value.userId}}" + comments: + type: Comment + list: true + call: + steps: + - query: commentsFromPost + args: + postId: "{{.value.id}}" + Comment: + fields: + id: + type: Int + required: true + postId: + type: Int + required: true + name: + type: String + required: true + email: + type: String + required: true body: type: String required: true + post: + type: Post + call: + steps: + - query: post + args: + id: "{{.value.postId}}" + Album: + fields: id: type: Int required: true + userId: + type: Int + required: true title: type: String required: true user: type: User - http: - path: /users/{{.value.userId}} + call: + steps: + - query: user + args: + id: "{{.value.userId}}" + photos: + type: Photo + list: true + call: + steps: + - query: photosFromAlbum + args: + albumId: "{{.value.id}}" + Photo: + fields: + id: + type: Int + required: true + albumId: + type: Int + required: true + title: + type: String + required: true + url: + type: String + required: true + thumbnailUrl: + type: String + required: true + album: + type: Album + call: + steps: + - query: album + args: + id: "{{.value.albumId}}" + Todo: + fields: + id: + type: Int + required: true userId: type: Int required: true - Query: + title: + type: String + required: true + completed: + type: Boolean + required: true + user: + type: User + call: + steps: + - query: user + args: + id: "{{.value.userId}}" + User: fields: + id: + type: Int + required: true + name: + type: String + required: true + username: + type: String + required: true + email: + type: String + required: true + address: + type: Address + phone: + type: String + website: + type: String + company: + type: Company posts: type: Post list: true - http: - path: /posts - User: + call: + steps: + - query: postsFromUser + args: + userId: "{{.value.id}}" + albums: + type: Album + list: true + call: + steps: + - query: albumsFromUser + args: + userId: "{{.value.id}}" + todos: + type: Todo + list: true + call: + steps: + - query: todosFromUser + args: + userId: "{{.value.id}}" + Address: fields: - email: + street: type: String + suite: + type: String + city: + type: String + zipcode: + type: String + geo: + type: Geo + Geo: + fields: + lat: + type: String + lng: + type: String + Company: + fields: + name: + type: String + catchPhrase: + type: String + bs: + type: String + PostInput: + fields: + userId: + type: Int required: true - id: + title: + type: String + required: true + body: + type: String + required: true + PartialPostInput: + fields: + userId: + type: Int + title: + type: String + body: + type: String + CommentInput: + fields: + postId: type: Int required: true name: type: String required: true - phone: + email: + type: String + required: true + body: type: String + required: true + AlbumInput: + fields: + userId: + type: Int + required: true + title: + type: String + required: true + PhotoInput: + fields: + albumId: + type: Int + required: true + title: + type: String + required: true + url: + type: String + required: true + thumbnailUrl: + type: String + required: true + TodoInput: + fields: + userId: + type: Int + required: true + title: + type: String + required: true + completed: + type: Boolean + required: true + UserInput: + fields: + name: + type: String + required: true username: type: String required: true + email: + type: String + required: true + address: + type: AddressInput + phone: + type: String website: type: String + company: + type: CompanyInput + AddressInput: + fields: + street: + type: String + suite: + type: String + city: + type: String + zipcode: + type: String + geo: + type: GeoInput + GeoInput: + fields: + lat: + type: String + lng: + type: String + CompanyInput: + fields: + name: + type: String + catchPhrase: + type: String + bs: + type: String + ComplexQuery: + fields: + userWithPostsAndComments: + type: UserWithPostsAndComments + args: + userId: + type: Int + required: true + call: + steps: + - query: user + args: + id: "{{.args.userId}}" + - query: postsFromUser + args: + userId: "{{.value.id}}" + - query: commentsFromPost + args: + postId: "{{.value.posts.0.id}}" + UserWithPostsAndComments: + fields: + user: + type: User + posts: + type: Post + list: true + comments: + type: Comment + list: true ``` @@ -246,75 +1088,623 @@ types: "hostname": "0.0.0.0" }, "upstream": { - "baseURL": "http://jsonplaceholder.typicode.com", + "baseURL": "https://jsonplaceholder.typicode.com", "httpCache": 42 }, "schema": { - "query": "Query" + "query": "Query", + "mutation": "Mutation" }, "types": { - "Post": { + "Query": { "fields": { - "body": { - "type": "String", - "required": true + "post": { + "type": "Post", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/posts/{{.args.id}}" + } }, - "id": { - "type": "Int", - "required": true + "postsFromUser": { + "type": "Post", + "list": true, + "args": { + "userId": { "type": "Int", "required": true } + }, + "http": { + "path": "/posts?userId={{.args.userId}}" + } }, - "title": { - "type": "String", - "required": true + "comment": { + "type": "Comment", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/comments/{{.args.id}}" + } + }, + "commentsFromPost": { + "type": "Comment", + "list": true, + "args": { + "postId": { "type": "Int", "required": true } + }, + "http": { + "path": "/comments?postId={{.args.postId}}" + } + }, + "album": { + "type": "Album", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/albums/{{.args.id}}" + } + }, + "albumsFromUser": { + "type": "Album", + "list": true, + "args": { + "userId": { "type": "Int", "required": true } + }, + "http": { + "path": "/albums?userId={{.args.userId}}" + } + }, + "photo": { + "type": "Photo", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/photos/{{.args.id}}" + } + }, + "photosFromAlbum": { + "type": "Photo", + "list": true, + "args": { + "albumId": { "type": "Int", "required": true } + }, + "http": { + "path": "/photos?albumId={{.args.albumId}}" + } + }, + "todo": { + "type": "Todo", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "path": "/todos/{{.args.id}}" + } + }, + "todosFromUser": { + "type": "Todo", + "list": true, + "args": { + "userId": { "type": "Int", "required": true } + }, + "http": { + "path": "/todos?userId={{.args.userId}}" + } }, "user": { "type": "User", + "args": { + "id": { "type": "Int", "required": true } + }, "http": { - "path": "/users/{{.value.userId}}" + "path": "/users/{{.args.id}}" } }, - "userId": { - "type": "Int", - "required": true + "users": { + "type": "User", + "list": true, + "http": { + "path": "/users" + } } } }, - "Query": { + "Mutation": { "fields": { - "posts": { + "createPost": { "type": "Post", - "list": true, + "args": { + "input": { "type": "PostInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/posts", + "body": "{{.args.input}}" + } + }, + "updatePost": { + "type": "Post", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "PostInput", "required": true } + }, + "http": { + "method": "PUT", + "path": "/posts/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "patchPost": { + "type": "Post", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "PartialPostInput", "required": true } + }, + "http": { + "method": "PATCH", + "path": "/posts/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deletePost": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/posts/{{.args.id}}" + } + }, + "createComment": { + "type": "Comment", + "args": { + "input": { "type": "CommentInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/comments", + "body": "{{.args.input}}" + } + }, + "updateComment": { + "type": "Comment", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "CommentInput", "required": true } + }, + "http": { + "method": "PUT", + "path": "/comments/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deleteComment": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/comments/{{.args.id}}" + } + }, + "createAlbum": { + "type": "Album", + "args": { + "input": { "type": "AlbumInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/albums", + "body": "{{.args.input}}" + } + }, + "updateAlbum": { + "type": "Album", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "AlbumInput", "required": true } + }, "http": { - "path": "/posts" + "method": "PUT", + "path": "/albums/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deleteAlbum": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/albums/{{.args.id}}" + } + }, + "createPhoto": { + "type": "Photo", + "args": { + "input": { "type": "PhotoInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/photos", + "body": "{{.args.input}}" + } + }, + "updatePhoto": { + "type": "Photo", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "PhotoInput", "required": true } + }, + "http": { + "method": "PUT", + "path": "/photos/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deletePhoto": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/photos/{{.args.id}}" + } + }, + "createTodo": { + "type": "Todo", + "args": { + "input": { "type": "TodoInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/todos", + "body": "{{.args.input}}" + } + }, + "updateTodo": { + "type": "Todo", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "TodoInput", "required": true } + }, + "http": { + "method": "PUT", + "path": "/todos/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deleteTodo": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/todos/{{.args.id}}" + } + }, + "createUser": { + "type": "User", + "args": { + "input": { "type": "UserInput", "required": true } + }, + "http": { + "method": "POST", + "path": "/users", + "body": "{{.args.input}}" + } + }, + "updateUser": { + "type": "User", + "args": { + "id": { "type": "Int", "required": true }, + "input": { "type": "UserInput", "required": true } + }, + "http": { + "method": "PUT", + "path": "/users/{{.args.id}}", + "body": "{{.args.input}}" + } + }, + "deleteUser": { + "type": "Boolean", + "args": { + "id": { "type": "Int", "required": true } + }, + "http": { + "method": "DELETE", + "path": "/users/{{.args.id}}" } } } }, - "User": { + "Post": { "fields": { - "email": { - "type": "String", - "required": true - }, - "id": { - "type": "Int", - "required": true + "id": { "type": "Int", "required": true }, + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "body": { "type": "String", "required": true }, + "user": { + "type": "User", + "call": { + "steps": [ + { "query": "user", "args": { "id": "{{.value.userId}}" } } + ] + } }, - "name": { - "type": "String", - "required": true + "comments": { + "type": "Comment", + "list": true, + "call": { + "steps": [ + { + "query": "commentsFromPost", + "args": { "postId": "{{.value.id}}" } + } + ] + } + } + } + }, + "Comment": { + "fields": { + "id": { "type": "Int", "required": true }, + "postId": { "type": "Int", "required": true }, + "name": { "type": "String", "required": true }, + "email": { "type": "String", "required": true }, + "body": { "type": "String", "required": true }, + "post": { + "type": "Post", + "call": { + "steps": [ + { "query": "post", "args": { "id": "{{.value.postId}}" } } + ] + } + } + } + }, + "Album": { + "fields": { + "id": { "type": "Int", "required": true }, + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "user": { + "type": "User", + "call": { + "steps": [ + { "query": "user", "args": { "id": "{{.value.userId}}" } } + ] + } }, - "phone": { - "type": "String" + "photos": { + "type": "Photo", + "list": true, + "call": { + "steps": [ + { + "query": "photosFromAlbum", + "args": { "albumId": "{{.value.id}}" } + } + ] + } + } + } + }, + "Photo": { + "fields": { + "id": { "type": "Int", "required": true }, + "albumId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "url": { "type": "String", "required": true }, + "thumbnailUrl": { "type": "String", "required": true }, + "album": { + "type": "Album", + "call": { + "steps": [ + { "query": "album", "args": { "id": "{{.value.albumId}}" } } + ] + } + } + } + }, + "Todo": { + "fields": { + "id": { "type": "Int", "required": true }, + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "completed": { "type": "Boolean", "required": true }, + "user": { + "type": "User", + "call": { + "steps": [ + { "query": "user", "args": { "id": "{{.value.userId}}" } } + ] + } + } + } + }, + "User": { + "fields": { + "id": { "type": "Int", "required": true }, + "name": { "type": "String", "required": true }, + "username": { "type": "String", "required": true }, + "email": { "type": "String", "required": true }, + "address": { "type": "Address" }, + "phone": { "type": "String" }, + "website": { "type": "String" }, + "company": { "type": "Company" }, + "posts": { + "type": "Post", + "list": true, + "call": { + "steps": [ + { + "query": "postsFromUser", + "args": { "userId": "{{.value.id}}" } + } + ] + } }, - "username": { - "type": "String", - "required": true + "albums": { + "type": "Album", + "list": true, + "call": { + "steps": [ + { + "query": "albumsFromUser", + "args": { "userId": "{{.value.id}}" } + } + ] + } }, - "website": { - "type": "String" + "todos": { + "type": "Todo", + "list": true, + "call": { + "steps": [ + { + "query": "todosFromUser", + "args": { "userId": "{{.value.id}}" } + } + ] + } + } + } + }, + "Address": { + "fields": { + "street": { "type": "String" }, + "suite": { "type": "String" }, + "city": { "type": "String" }, + "zipcode": { "type": "String" }, + "geo": { "type": "Geo" } + } + }, + "Geo": { + "fields": { + "lat": { "type": "String" }, + "lng": { "type": "String" } + } + }, + "Company": { + "fields": { + "name": { "type": "String" }, + "catchPhrase": { "type": "String" }, + "bs": { "type": "String" } + } + }, + "PostInput": { + "fields": { + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "body": { "type": "String", "required": true } + } + }, + "PartialPostInput": { + "fields": { + "userId": { "type": "Int" }, + "title": { "type": "String" }, + "body": { "type": "String" } + } + }, + "CommentInput": { + "fields": { + "postId": { "type": "Int", "required": true }, + "name": { "type": "String", "required": true }, + "email": { "type": "String", "required": true }, + "body": { "type": "String", "required": true } + } + }, + "AlbumInput": { + "fields": { + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true } + } + }, + "PhotoInput": { + "fields": { + "albumId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "url": { "type": "String", "required": true }, + "thumbnailUrl": { "type": "String", "required": true } + } + }, + "TodoInput": { + "fields": { + "userId": { "type": "Int", "required": true }, + "title": { "type": "String", "required": true }, + "completed": { "type": "Boolean", "required": true } + } + }, + "UserInput": { + "fields": { + "name": { "type": "String", "required": true }, + "username": { "type": "String", "required": true }, + "email": { "type": "String", "required": true }, + "address": { "type": "AddressInput" }, + "phone": { "type": "String" }, + "website": { "type": "String" }, + "company": { "type": "CompanyInput" } + } + }, + "AddressInput": { + "fields": { + "street": { "type": "String" }, + "suite": { "type": "String" }, + "city": { "type": "String" }, + "zipcode": { "type": "String" }, + "geo": { "type": "GeoInput" } + } + }, + "GeoInput": { + "fields": { + "lat": { "type": "String" }, + "lng": { "type": "String" } + } + }, + "CompanyInput": { + "fields": { + "name": { "type": "String" }, + "catchPhrase": { "type": "String" }, + "bs": { "type": "String" } + } + }, + "ComplexQuery": { + "fields": { + "userWithPostsAndComments": { + "type": "UserWithPostsAndComments", + "args": { + "userId": { "type": "Int", "required": true } + }, + "call": { + "steps": [ + { "query": "user", "args": { "id": "{{.args.userId}}" } }, + { + "query": "postsFromUser", + "args": { "userId": "{{.value.id}}" } + }, + { + "query": "commentsFromPost", + "args": { "postId": "{{.value.posts.0.id}}" } + } + ] + } } } + }, + "UserWithPostsAndComments": { + "fields": { + "user": { "type": "User" }, + "posts": { "type": "Post", "list": true }, + "comments": { "type": "Comment", "list": true } + } } } } diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index afc5a27ad2..000eab05fa 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -21,16 +21,16 @@ const Configuration = (): JSX.Element => { npm i -g @tailcallhq/tailcall - {CodeTabItem({code: GRAPHQL_CONFIG, language: "graphql"})} - {CodeTabItem({code: YML_CONFIG, language: "yaml"})} - {CodeTabItem({code: JSON_CONFIG, language: "json"})} + {CodeTabItem({ code: GRAPHQL_CONFIG, language: "graphql" })} + {CodeTabItem({ code: YML_CONFIG, language: "yaml" })} + {CodeTabItem({ code: JSON_CONFIG, language: "json" })} ) } -const CodeTabItem = ({code, language}: {code: string; language: "json" | "yaml" | "graphql"}) => ( +const CodeTabItem = ({ code, language }: { code: string; language: "json" | "yaml" | "graphql" }) => ( Date: Tue, 30 Jul 2024 18:05:33 +0100 Subject: [PATCH 5/9] fix: prettier fix --- src/components/home/Configuration.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index 000eab05fa..1062c8a494 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -21,16 +21,16 @@ const Configuration = (): JSX.Element => { npm i -g @tailcallhq/tailcall - {CodeTabItem({ code: GRAPHQL_CONFIG, language: "graphql" })} - {CodeTabItem({ code: YML_CONFIG, language: "yaml" })} - {CodeTabItem({ code: JSON_CONFIG, language: "json" })} + {CodeTabItem({code: GRAPHQL_CONFIG, language: "graphql"})} + {CodeTabItem({code: YML_CONFIG, language: "yaml"})} + {CodeTabItem({code: JSON_CONFIG, language: "json"})} ) } -const CodeTabItem = ({ code, language }: { code: string; language: "json" | "yaml" | "graphql" }) => ( +const CodeTabItem = ({code, language}: {code: string; language: "json" | "yaml" | "graphql"}) => ( Date: Tue, 30 Jul 2024 18:08:16 +0100 Subject: [PATCH 6/9] fix: prettier fix --- docs/getting-started.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 3224cc5250..559188a078 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -201,7 +201,7 @@ type Mutation { # Create a new album createAlbum(input: AlbumInput!): Album @http(method: "POST", path: "/albums", body: "{{.args.input}}") - # Update an exixting album + # Update an existing album updateAlbum(id: Int!, input: AlbumInput!): Album @http(method: "PUT", path: "/albums/{{.args.id}}", body: "{{.args.input}}") # Delete an album deleteAlbum(id: Int!): Boolean @http(method: "DELETE", path: "/albums/{{.args.id}}") From 972cec2f6c6bc675320ed933ed18203005e84867 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Wed, 31 Jul 2024 10:33:15 +0100 Subject: [PATCH 7/9] chore: Add detailed explanation for @call operator in GraphQL schema --- docs/getting-started.mdx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 559188a078..673f8479f4 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -139,6 +139,16 @@ We can use the following formats to define our GraphQL schema: `.graphql`, `.yml Create one of the following files and paste the contents into it. +:::note +The @call directive is used to chain multiple queries together. +It allows the result of one query to be used as an argument for another query. +For example, in the Post type, the @call directive fetches the user who created the post +by using the userId from the post as an argument for the user query. +The steps attribute is an array of query steps that are executed sequentially. +Each step specifies a query to be called and the arguments to be passed to it. +The `{{.value}}` placeholder is used to reference the result of the previous step in the chain. +::: + From 6f6728988b66739e247a8cf8840b851d07d37fb0 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Wed, 21 Aug 2024 12:49:09 +0100 Subject: [PATCH 8/9] refactor(docs): simplify field names Signed-off-by: David Anyatonwu --- docs/getting-started.mdx | 152 +++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 673f8479f4..a4ecb51ba1 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -168,23 +168,23 @@ type Query { # Fetch a post by ID post(id: Int!): Post @http(path: "/posts/{{.args.id}}") # Fetch all posts by a specific user - postsFromUser(userId: Int!): [Post] @http(path: "/posts?userId={{.args.userId}}") + posts(user: Id): [Post] @http(path: "/posts?userId={{.args.user}}") # Fetch a comment by ID comment(id: Int!): Comment @http(path: "/comments/{{.args.id}}") # Fetch all comments on a specific post - commentsFromPost(postId: Int!): [Comment] @http(path: "/comments?postId={{.args.postId}}") + comments(post: Id): [Comment] @http(path: "/comments?postId={{.args.post}}") # Fetch an album by ID album(id: Int!): Album @http(path: "/albums/{{.args.id}}") # Fetch all albums by a specific user - albumsFromUser(userId: Int!): [Album] @http(path: "/albums?userId={{.args.userId}}") + albums(user: Id): [Album] @http(path: "/albums?userId={{.args.user}}") # Fetch a photo by ID photo(id: Int!): Photo @http(path: "/photos/{{.args.id}}") # Fetch all photos in a specific album - photosFromAlbum(albumId: Int!): [Photo] @http(path: "/photos?albumId={{.args.albumId}}") + photos(album: Id): [Photo] @http(path: "/photos?albumId={{.args.album}}") # Fetch a todo by ID todo(id: Int!): Todo @http(path: "/todos/{{.args.id}}") # Fetch all todos by a specific user - todosFromUser(userId: Int!): [Todo] @http(path: "/todos?userId={{.args.userId}}") + todos(user: Id): [Todo] @http(path: "/todos?userId={{.args.user}}") # Fetch a user by ID user(id: Int!): User @http(path: "/users/{{.args.id}}") # Fetch all users @@ -247,7 +247,7 @@ type Post { # Fetch the user who created the post user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) # Fetch all comments on the post - comments: [Comment] @call(steps: [{query: "commentsFromPost", args: {postId: "{{.value.id}}"}}]) + comments: [Comment] @call(steps: [{query: "comments", args: {post: "{{.value.id}}"}}]) } # Define the Comment type with its fields and related queries @@ -269,7 +269,7 @@ type Album { # Fetch the user who created the album user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) # Fetch all photos in the album - photos: [Photo] @call(steps: [{query: "photosFromAlbum", args: {albumId: "{{.value.id}}"}}]) + photos: [Photo] @call(steps: [{query: "photos", args: {album: "{{.value.id}}"}}]) } # Define the Photo type with its fields and related queries @@ -304,11 +304,11 @@ type User { website: String company: Company # Fetch all posts by the user - posts: [Post] @call(steps: [{query: "postsFromUser", args: {userId: "{{.value.id}}"}}]) + posts: [Post] @call(steps: [{query: "posts", args: {user: "{{.value.id}}"}}]) # Fetch all albums by the user - albums: [Album] @call(steps: [{query: "albumsFromUser", args: {userId: "{{.value.id}}"}}]) + albums: [Album] @call(steps: [{query: "albums", args: {user: "{{.value.id}}"}}]) # Fetch all todos by the user - todos: [Todo] @call(steps: [{query: "todosFromUser", args: {userId: "{{.value.id}}"}}]) + todos: [Todo] @call(steps: [{query: "todos", args: {user: "{{.value.id}}"}}]) } # Define the Address type with its fields @@ -414,8 +414,8 @@ type ComplexQuery { userWithPostsAndComments(userId: Int!): UserWithPostsAndComments @call(steps: [ {query: "user", args: {id: "{{.args.userId}}"}}, - {query: "postsFromUser", args: {userId: "{{.value.id}}"}}, - {query: "commentsFromPost", args: {postId: "{{.value.posts.0.id}}"}} + {query: "posts", args: {user: "{{.value.id}}"}}, + {query: "comments", args: {post: "{{.value.posts.0.id}}"}} ]) } @@ -452,15 +452,15 @@ types: required: true http: path: /posts/{{.args.id}} - postsFromUser: + posts: type: Post list: true args: - userId: - type: Int + user: + type: Id required: true http: - path: /posts?userId={{.args.userId}} + path: /posts?userId={{.args.user}} comment: type: Comment args: @@ -469,15 +469,15 @@ types: required: true http: path: /comments/{{.args.id}} - commentsFromPost: + comments: type: Comment list: true args: - postId: - type: Int + post: + type: Id required: true http: - path: /comments?postId={{.args.postId}} + path: /comments?postId={{.args.post}} album: type: Album args: @@ -486,15 +486,15 @@ types: required: true http: path: /albums/{{.args.id}} - albumsFromUser: + albums: type: Album list: true args: - userId: - type: Int + user: + type: Id required: true http: - path: /albums?userId={{.args.userId}} + path: /albums?userId={{.args.user}} photo: type: Photo args: @@ -503,15 +503,15 @@ types: required: true http: path: /photos/{{.args.id}} - photosFromAlbum: + photos: type: Photo list: true args: - albumId: - type: Int + album: + type: Id required: true http: - path: /photos?albumId={{.args.albumId}} + path: /photos?albumId={{.args.album}} todo: type: Todo args: @@ -520,15 +520,15 @@ types: required: true http: path: /todos/{{.args.id}} - todosFromUser: + todos: type: Todo list: true args: - userId: - type: Int + user: + type: Id required: true http: - path: /todos?userId={{.args.userId}} + path: /todos?userId={{.args.user}} user: type: User args: @@ -775,9 +775,9 @@ types: list: true call: steps: - - query: commentsFromPost + - query: comments args: - postId: "{{.value.id}}" + post: "{{.value.id}}" Comment: fields: id: @@ -825,9 +825,9 @@ types: list: true call: steps: - - query: photosFromAlbum + - query: photos args: - albumId: "{{.value.id}}" + album: "{{.value.id}}" Photo: fields: id: @@ -900,25 +900,25 @@ types: list: true call: steps: - - query: postsFromUser + - query: posts args: - userId: "{{.value.id}}" + user: "{{.value.id}}" albums: type: Album list: true call: steps: - - query: albumsFromUser + - query: albums args: - userId: "{{.value.id}}" + user: "{{.value.id}}" todos: type: Todo list: true call: steps: - - query: todosFromUser + - query: todos args: - userId: "{{.value.id}}" + user: "{{.value.id}}" Address: fields: street: @@ -1069,12 +1069,12 @@ types: - query: user args: id: "{{.args.userId}}" - - query: postsFromUser + - query: posts args: - userId: "{{.value.id}}" - - query: commentsFromPost + user: "{{.value.id}}" + - query: comments args: - postId: "{{.value.posts.0.id}}" + post: "{{.value.posts.0.id}}" UserWithPostsAndComments: fields: user: @@ -1117,14 +1117,14 @@ types: "path": "/posts/{{.args.id}}" } }, - "postsFromUser": { + "posts": { "type": "Post", "list": true, "args": { - "userId": { "type": "Int", "required": true } + "user": { "type": "Id", "required": true } }, "http": { - "path": "/posts?userId={{.args.userId}}" + "path": "/posts?userId={{.args.user}}" } }, "comment": { @@ -1136,14 +1136,14 @@ types: "path": "/comments/{{.args.id}}" } }, - "commentsFromPost": { + "comments": { "type": "Comment", "list": true, "args": { - "postId": { "type": "Int", "required": true } + "post": { "type": "Id", "required": true } }, "http": { - "path": "/comments?postId={{.args.postId}}" + "path": "/comments?postId={{.args.post}}" } }, "album": { @@ -1155,14 +1155,14 @@ types: "path": "/albums/{{.args.id}}" } }, - "albumsFromUser": { + "albums": { "type": "Album", "list": true, "args": { - "userId": { "type": "Int", "required": true } + "user": { "type": "Id", "required": true } }, "http": { - "path": "/albums?userId={{.args.userId}}" + "path": "/albums?userId={{.args.user}}" } }, "photo": { @@ -1174,14 +1174,14 @@ types: "path": "/photos/{{.args.id}}" } }, - "photosFromAlbum": { + "photos": { "type": "Photo", "list": true, "args": { - "albumId": { "type": "Int", "required": true } + "album": { "type": "Id", "required": true } }, "http": { - "path": "/photos?albumId={{.args.albumId}}" + "path": "/photos?albumId={{.args.album}}" } }, "todo": { @@ -1193,14 +1193,14 @@ types: "path": "/todos/{{.args.id}}" } }, - "todosFromUser": { + "todos": { "type": "Todo", "list": true, "args": { - "userId": { "type": "Int", "required": true } + "user": { "type": "Id", "required": true } }, "http": { - "path": "/todos?userId={{.args.userId}}" + "path": "/todos?userId={{.args.user}}" } }, "user": { @@ -1455,8 +1455,8 @@ types: "call": { "steps": [ { - "query": "commentsFromPost", - "args": { "postId": "{{.value.id}}" } + "query": "comments", + "args": { "post": "{{.value.id}}" } } ] } @@ -1499,8 +1499,8 @@ types: "call": { "steps": [ { - "query": "photosFromAlbum", - "args": { "albumId": "{{.value.id}}" } + "query": "photos", + "args": { "album": "{{.value.id}}" } } ] } @@ -1556,8 +1556,8 @@ types: "call": { "steps": [ { - "query": "postsFromUser", - "args": { "userId": "{{.value.id}}" } + "query": "posts", + "args": { "user": "{{.value.id}}" } } ] } @@ -1568,8 +1568,8 @@ types: "call": { "steps": [ { - "query": "albumsFromUser", - "args": { "userId": "{{.value.id}}" } + "query": "albums", + "args": { "user": "{{.value.id}}" } } ] } @@ -1580,8 +1580,8 @@ types: "call": { "steps": [ { - "query": "todosFromUser", - "args": { "userId": "{{.value.id}}" } + "query": "todos", + "args": { "user": "{{.value.id}}" } } ] } @@ -1697,12 +1697,12 @@ types: "steps": [ { "query": "user", "args": { "id": "{{.args.userId}}" } }, { - "query": "postsFromUser", - "args": { "userId": "{{.value.id}}" } + "query": "posts", + "args": { "user": "{{.value.id}}" } }, { - "query": "commentsFromPost", - "args": { "postId": "{{.value.posts.0.id}}" } + "query": "comments", + "args": { "post": "{{.value.posts.0.id}}" } } ] } @@ -1821,4 +1821,4 @@ The server starts with the schema provided and prints out a load of meta informa Now that you have a running GraphQL server, you can follow our [Github Actions Guide](./gh-action.md) to deploy the application on one of the following cloud providers. - [AWS Lambda](./tailcall-on-aws.md) -- [Fly.io](./tailcall-on-fly.md) +- [Fly.io](./tailcall-on-fly.md) \ No newline at end of file From 91f1602782851bc31d3463b26aaf38e2571b9b25 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Wed, 21 Aug 2024 13:39:18 +0100 Subject: [PATCH 9/9] docs(getting-started): update JSONPlaceholder example instructions Signed-off-by: David Anyatonwu --- docs/getting-started.mdx | 1596 +------------------------------------- 1 file changed, 27 insertions(+), 1569 deletions(-) diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index a4ecb51ba1..905b841bfd 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -136,1593 +136,51 @@ This approach allows us to fetch and manipulate data in a more structured and fl We can use the following formats to define our GraphQL schema: `.graphql`, `.yml`, `.json`. -Create one of the following files and paste the contents into it. +You can find complete example configurations for the JSONPlaceholder API in the following formats: +- [GraphQL](https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.graphql) +- [YAML](https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.yml) +- [JSON](https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.json) -:::note -The @call directive is used to chain multiple queries together. -It allows the result of one query to be used as an argument for another query. -For example, in the Post type, the @call directive fetches the user who created the post -by using the userId from the post as an argument for the user query. -The steps attribute is an array of query steps that are executed sequentially. -Each step specifies a query to be called and the arguments to be passed to it. -The `{{.value}}` placeholder is used to reference the result of the previous step in the chain. -::: +These examples demonstrate how to set up a Tailcall configuration for a complex API with multiple interconnected resources. You can use these as a starting point for your own configurations. + +To use one of these configurations, you can either copy the content directly into a local file, or download the file using a tool like `curl`. For example: - + -```graphql showLineNumbers -# Define the schema with upstream configuration and server settings -schema - # Specify server configuration: Start GraphQL server at 0.0.0.0:8000 - @server(port: 8000) - # Specify a base URL for all HTTP requests - @upstream(baseURL: "https://jsonplaceholder.typicode.com") { - query: Query - mutation: Mutation -} - -# Define the Query type with various fields -type Query { - # Fetch a post by ID - post(id: Int!): Post @http(path: "/posts/{{.args.id}}") - # Fetch all posts by a specific user - posts(user: Id): [Post] @http(path: "/posts?userId={{.args.user}}") - # Fetch a comment by ID - comment(id: Int!): Comment @http(path: "/comments/{{.args.id}}") - # Fetch all comments on a specific post - comments(post: Id): [Comment] @http(path: "/comments?postId={{.args.post}}") - # Fetch an album by ID - album(id: Int!): Album @http(path: "/albums/{{.args.id}}") - # Fetch all albums by a specific user - albums(user: Id): [Album] @http(path: "/albums?userId={{.args.user}}") - # Fetch a photo by ID - photo(id: Int!): Photo @http(path: "/photos/{{.args.id}}") - # Fetch all photos in a specific album - photos(album: Id): [Photo] @http(path: "/photos?albumId={{.args.album}}") - # Fetch a todo by ID - todo(id: Int!): Todo @http(path: "/todos/{{.args.id}}") - # Fetch all todos by a specific user - todos(user: Id): [Todo] @http(path: "/todos?userId={{.args.user}}") - # Fetch a user by ID - user(id: Int!): User @http(path: "/users/{{.args.id}}") - # Fetch all users - users: [User] @http(path: "/users") -} - -# Define the Mutation type with various fields for creating, updating, and deleting resources -type Mutation { - # Create a new post - createPost(input: PostInput!): Post @http(method: "POST", path: "/posts", body: "{{.args.input}}") - # Update an existing post - updatePost(id: Int!, input: PostInput!): Post @http(method: "PUT", path: "/posts/{{.args.id}}", body: "{{.args.input}}") - # Patch (partially update) an existing post - patchPost(id: Int!, input: PartialPostInput!): Post @http(method: "PATCH", path: "/posts/{{.args.id}}", body: "{{.args.input}}") - # Delete a post - deletePost(id: Int!): Boolean @http(method: "DELETE", path: "/posts/{{.args.id}}") - - # Create a new comment - createComment(input: CommentInput!): Comment @http(method: "POST", path: "/comments", body: "{{.args.input}}") - # Update an existing comment - updateComment(id: Int!, input: CommentInput!): Comment @http(method: "PUT", path: "/comments/{{.args.id}}", body: "{{.args.input}}") - # Delete a comment - deleteComment(id: Int!): Boolean @http(method: "DELETE", path: "/comments/{{.args.id}}") - - # Create a new album - createAlbum(input: AlbumInput!): Album @http(method: "POST", path: "/albums", body: "{{.args.input}}") - # Update an existing album - updateAlbum(id: Int!, input: AlbumInput!): Album @http(method: "PUT", path: "/albums/{{.args.id}}", body: "{{.args.input}}") - # Delete an album - deleteAlbum(id: Int!): Boolean @http(method: "DELETE", path: "/albums/{{.args.id}}") - - # Create a new photo - createPhoto(input: PhotoInput!): Photo @http(method: "POST", path: "/photos", body: "{{.args.input}}") - # Update an existing photo - updatePhoto(id: Int!, input: PhotoInput!): Photo @http(method: "PUT", path: "/photos/{{.args.id}}", body: "{{.args.input}}") - # Delete a photo - deletePhoto(id: Int!): Boolean @http(method: "DELETE", path: "/photos/{{.args.id}}") - - # Create a new todo - createTodo(input: TodoInput!): Todo @http(method: "POST", path: "/todos", body: "{{.args.input}}") - # Update an existing todo - updateTodo(id: Int!, input: TodoInput!): Todo @http(method: "PUT", path: "/todos/{{.args.id}}", body: "{{.args.input}}") - # Delete a todo - deleteTodo(id: Int!): Boolean @http(method: "DELETE", path: "/todos/{{.args.id}}") - - # Create a new user - createUser(input: UserInput!): User @http(method: "POST", path: "/users", body: "{{.args.input}}") - # Update an existing user - updateUser(id: Int!, input: UserInput!): User @http(method: "PUT", path: "/users/{{.args.id}}", body: "{{.args.input}}") - # Delete a user - deleteUser(id: Int!): Boolean @http(method: "DELETE", path: "/users/{{.args.id}}") -} - -# Define the Post type with its fields and related queries -type Post { - id: Int! - userId: Int! - title: String! - body: String! - # Fetch the user who created the post - user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) - # Fetch all comments on the post - comments: [Comment] @call(steps: [{query: "comments", args: {post: "{{.value.id}}"}}]) -} - -# Define the Comment type with its fields and related queries -type Comment { - id: Int! - postId: Int! - name: String! - email: String! - body: String! - # Fetch the post to which the comment belongs - post: Post @call(steps: [{query: "post", args: {id: "{{.value.postId}}"}}]) -} - -# Define the Album type with its fields and related queries -type Album { - id: Int! - userId: Int! - title: String! - # Fetch the user who created the album - user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) - # Fetch all photos in the album - photos: [Photo] @call(steps: [{query: "photos", args: {album: "{{.value.id}}"}}]) -} - -# Define the Photo type with its fields and related queries -type Photo { - id: Int! - albumId: Int! - title: String! - url: String! - thumbnailUrl: String! - # Fetch the album to which the photo belongs - album: Album @call(steps: [{query: "album", args: {id: "{{.value.albumId}}"}}]) -} - -# Define the Todo type with its fields and related queries -type Todo { - id: Int! - userId: Int! - title: String! - completed: Boolean! - # Fetch the user who created the todo - user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) -} - -# Define the User type with its fields and related queries -type User { - id: Int! - name: String! - username: String! - email: String! - address: Address - phone: String - website: String - company: Company - # Fetch all posts by the user - posts: [Post] @call(steps: [{query: "posts", args: {user: "{{.value.id}}"}}]) - # Fetch all albums by the user - albums: [Album] @call(steps: [{query: "albums", args: {user: "{{.value.id}}"}}]) - # Fetch all todos by the user - todos: [Todo] @call(steps: [{query: "todos", args: {user: "{{.value.id}}"}}]) -} - -# Define the Address type with its fields -type Address { - street: String - suite: String - city: String - zipcode: String - geo: Geo -} - -# Define the Geo type with its fields -type Geo { - lat: String - lng: String -} - -# Define the Company type with its fields -type Company { - name: String - catchPhrase: String - bs: String -} - -# Define the input type for creating a post -input PostInput { - userId: Int! - title: String! - body: String! -} - -# Define the input type for partially updating a post -input PartialPostInput { - userId: Int - title: String - body: String -} - -# Define the input type for creating a comment -input CommentInput { - postId: Int! - name: String! - email: String! - body: String! -} - -# Define the input type for creating an album -input AlbumInput { - userId: Int! - title: String! -} - -# Define the input type for creating a photo -input PhotoInput { - albumId: Int! - title: String! - url: String! - thumbnailUrl: String! -} - -# Define the input type for creating a todo -input TodoInput { - userId: Int! - title: String! - completed: Boolean! -} - -# Define the input type for creating a user -input UserInput { - name: String! - username: String! - email: String! - address: AddressInput - phone: String - website: String - company: CompanyInput -} - -# Define the input type for the address field -input AddressInput { - street: String - suite: String - city: String - zipcode: String - geo: GeoInput -} - -# Define the input type for the geo field -input GeoInput { - lat: String - lng: String -} - -# Define the input type for the company field -input CompanyInput { - name: String - catchPhrase: String - bs: String -} - -# Define a complex query type for fetching user data along with posts and comments -type ComplexQuery { - userWithPostsAndComments(userId: Int!): UserWithPostsAndComments - @call(steps: [ - {query: "user", args: {id: "{{.args.userId}}"}}, - {query: "posts", args: {user: "{{.value.id}}"}}, - {query: "comments", args: {post: "{{.value.posts.0.id}}"}} - ]) -} - -# Define the UserWithPostsAndComments type with fields for user, posts, and comments -type UserWithPostsAndComments { - user: User - posts: [Post] - comments: [Comment] -} +```bash +curl -O https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.graphql ``` - + -```yml showLineNumbers -server: - port: 8000 - queryValidation: false - hostname: "0.0.0.0" -upstream: - baseURL: https://jsonplaceholder.typicode.com - httpCache: 42 -schema: - query: Query - mutation: Mutation -types: - Query: - fields: - post: - type: Post - args: - id: - type: Int - required: true - http: - path: /posts/{{.args.id}} - posts: - type: Post - list: true - args: - user: - type: Id - required: true - http: - path: /posts?userId={{.args.user}} - comment: - type: Comment - args: - id: - type: Int - required: true - http: - path: /comments/{{.args.id}} - comments: - type: Comment - list: true - args: - post: - type: Id - required: true - http: - path: /comments?postId={{.args.post}} - album: - type: Album - args: - id: - type: Int - required: true - http: - path: /albums/{{.args.id}} - albums: - type: Album - list: true - args: - user: - type: Id - required: true - http: - path: /albums?userId={{.args.user}} - photo: - type: Photo - args: - id: - type: Int - required: true - http: - path: /photos/{{.args.id}} - photos: - type: Photo - list: true - args: - album: - type: Id - required: true - http: - path: /photos?albumId={{.args.album}} - todo: - type: Todo - args: - id: - type: Int - required: true - http: - path: /todos/{{.args.id}} - todos: - type: Todo - list: true - args: - user: - type: Id - required: true - http: - path: /todos?userId={{.args.user}} - user: - type: User - args: - id: - type: Int - required: true - http: - path: /users/{{.args.id}} - users: - type: User - list: true - http: - path: /users - Mutation: - fields: - createPost: - type: Post - args: - input: - type: PostInput - required: true - http: - method: POST - path: /posts - body: "{{.args.input}}" - updatePost: - type: Post - args: - id: - type: Int - required: true - input: - type: PostInput - required: true - http: - method: PUT - path: /posts/{{.args.id}} - body: "{{.args.input}}" - patchPost: - type: Post - args: - id: - type: Int - required: true - input: - type: PartialPostInput - required: true - http: - method: PATCH - path: /posts/{{.args.id}} - body: "{{.args.input}}" - deletePost: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /posts/{{.args.id}} - createComment: - type: Comment - args: - input: - type: CommentInput - required: true - http: - method: POST - path: /comments - body: "{{.args.input}}" - updateComment: - type: Comment - args: - id: - type: Int - required: true - input: - type: CommentInput - required: true - http: - method: PUT - path: /comments/{{.args.id}} - body: "{{.args.input}}" - deleteComment: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /comments/{{.args.id}} - createAlbum: - type: Album - args: - input: - type: AlbumInput - required: true - http: - method: POST - path: /albums - body: "{{.args.input}}" - updateAlbum: - type: Album - args: - id: - type: Int - required: true - input: - type: AlbumInput - required: true - http: - method: PUT - path: /albums/{{.args.id}} - body: "{{.args.input}}" - deleteAlbum: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /albums/{{.args.id}} - createPhoto: - type: Photo - args: - input: - type: PhotoInput - required: true - http: - method: POST - path: /photos - body: "{{.args.input}}" - updatePhoto: - type: Photo - args: - id: - type: Int - required: true - input: - type: PhotoInput - required: true - http: - method: PUT - path: /photos/{{.args.id}} - body: "{{.args.input}}" - deletePhoto: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /photos/{{.args.id}} - createTodo: - type: Todo - args: - input: - type: TodoInput - required: true - http: - method: POST - path: /todos - body: "{{.args.input}}" - updateTodo: - type: Todo - args: - id: - type: Int - required: true - input: - type: TodoInput - required: true - http: - method: PUT - path: /todos/{{.args.id}} - body: "{{.args.input}}" - deleteTodo: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /todos/{{.args.id}} - createUser: - type: User - args: - input: - type: UserInput - required: true - http: - method: POST - path: /users - body: "{{.args.input}}" - updateUser: - type: User - args: - id: - type: Int - required: true - input: - type: UserInput - required: true - http: - method: PUT - path: /users/{{.args.id}} - body: "{{.args.input}}" - deleteUser: - type: Boolean - args: - id: - type: Int - required: true - http: - method: DELETE - path: /users/{{.args.id}} - Post: - fields: - id: - type: Int - required: true - userId: - type: Int - required: true - title: - type: String - required: true - body: - type: String - required: true - user: - type: User - call: - steps: - - query: user - args: - id: "{{.value.userId}}" - comments: - type: Comment - list: true - call: - steps: - - query: comments - args: - post: "{{.value.id}}" - Comment: - fields: - id: - type: Int - required: true - postId: - type: Int - required: true - name: - type: String - required: true - email: - type: String - required: true - body: - type: String - required: true - post: - type: Post - call: - steps: - - query: post - args: - id: "{{.value.postId}}" - Album: - fields: - id: - type: Int - required: true - userId: - type: Int - required: true - title: - type: String - required: true - user: - type: User - call: - steps: - - query: user - args: - id: "{{.value.userId}}" - photos: - type: Photo - list: true - call: - steps: - - query: photos - args: - album: "{{.value.id}}" - Photo: - fields: - id: - type: Int - required: true - albumId: - type: Int - required: true - title: - type: String - required: true - url: - type: String - required: true - thumbnailUrl: - type: String - required: true - album: - type: Album - call: - steps: - - query: album - args: - id: "{{.value.albumId}}" - Todo: - fields: - id: - type: Int - required: true - userId: - type: Int - required: true - title: - type: String - required: true - completed: - type: Boolean - required: true - user: - type: User - call: - steps: - - query: user - args: - id: "{{.value.userId}}" - User: - fields: - id: - type: Int - required: true - name: - type: String - required: true - username: - type: String - required: true - email: - type: String - required: true - address: - type: Address - phone: - type: String - website: - type: String - company: - type: Company - posts: - type: Post - list: true - call: - steps: - - query: posts - args: - user: "{{.value.id}}" - albums: - type: Album - list: true - call: - steps: - - query: albums - args: - user: "{{.value.id}}" - todos: - type: Todo - list: true - call: - steps: - - query: todos - args: - user: "{{.value.id}}" - Address: - fields: - street: - type: String - suite: - type: String - city: - type: String - zipcode: - type: String - geo: - type: Geo - Geo: - fields: - lat: - type: String - lng: - type: String - Company: - fields: - name: - type: String - catchPhrase: - type: String - bs: - type: String - PostInput: - fields: - userId: - type: Int - required: true - title: - type: String - required: true - body: - type: String - required: true - PartialPostInput: - fields: - userId: - type: Int - title: - type: String - body: - type: String - CommentInput: - fields: - postId: - type: Int - required: true - name: - type: String - required: true - email: - type: String - required: true - body: - type: String - required: true - AlbumInput: - fields: - userId: - type: Int - required: true - title: - type: String - required: true - PhotoInput: - fields: - albumId: - type: Int - required: true - title: - type: String - required: true - url: - type: String - required: true - thumbnailUrl: - type: String - required: true - TodoInput: - fields: - userId: - type: Int - required: true - title: - type: String - required: true - completed: - type: Boolean - required: true - UserInput: - fields: - name: - type: String - required: true - username: - type: String - required: true - email: - type: String - required: true - address: - type: AddressInput - phone: - type: String - website: - type: String - company: - type: CompanyInput - AddressInput: - fields: - street: - type: String - suite: - type: String - city: - type: String - zipcode: - type: String - geo: - type: GeoInput - GeoInput: - fields: - lat: - type: String - lng: - type: String - CompanyInput: - fields: - name: - type: String - catchPhrase: - type: String - bs: - type: String - ComplexQuery: - fields: - userWithPostsAndComments: - type: UserWithPostsAndComments - args: - userId: - type: Int - required: true - call: - steps: - - query: user - args: - id: "{{.args.userId}}" - - query: posts - args: - user: "{{.value.id}}" - - query: comments - args: - post: "{{.value.posts.0.id}}" - UserWithPostsAndComments: - fields: - user: - type: User - posts: - type: Post - list: true - comments: - type: Comment - list: true +```bash +curl -O https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.yml ``` - + -```json showLineNumbers -{ - "server": { - "port": 8000, - "queryValidation": false, - "hostname": "0.0.0.0" - }, - "upstream": { - "baseURL": "https://jsonplaceholder.typicode.com", - "httpCache": 42 - }, - "schema": { - "query": "Query", - "mutation": "Mutation" - }, - "types": { - "Query": { - "fields": { - "post": { - "type": "Post", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/posts/{{.args.id}}" - } - }, - "posts": { - "type": "Post", - "list": true, - "args": { - "user": { "type": "Id", "required": true } - }, - "http": { - "path": "/posts?userId={{.args.user}}" - } - }, - "comment": { - "type": "Comment", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/comments/{{.args.id}}" - } - }, - "comments": { - "type": "Comment", - "list": true, - "args": { - "post": { "type": "Id", "required": true } - }, - "http": { - "path": "/comments?postId={{.args.post}}" - } - }, - "album": { - "type": "Album", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/albums/{{.args.id}}" - } - }, - "albums": { - "type": "Album", - "list": true, - "args": { - "user": { "type": "Id", "required": true } - }, - "http": { - "path": "/albums?userId={{.args.user}}" - } - }, - "photo": { - "type": "Photo", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/photos/{{.args.id}}" - } - }, - "photos": { - "type": "Photo", - "list": true, - "args": { - "album": { "type": "Id", "required": true } - }, - "http": { - "path": "/photos?albumId={{.args.album}}" - } - }, - "todo": { - "type": "Todo", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/todos/{{.args.id}}" - } - }, - "todos": { - "type": "Todo", - "list": true, - "args": { - "user": { "type": "Id", "required": true } - }, - "http": { - "path": "/todos?userId={{.args.user}}" - } - }, - "user": { - "type": "User", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "path": "/users/{{.args.id}}" - } - }, - "users": { - "type": "User", - "list": true, - "http": { - "path": "/users" - } - } - } - }, - "Mutation": { - "fields": { - "createPost": { - "type": "Post", - "args": { - "input": { "type": "PostInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/posts", - "body": "{{.args.input}}" - } - }, - "updatePost": { - "type": "Post", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "PostInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/posts/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "patchPost": { - "type": "Post", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "PartialPostInput", "required": true } - }, - "http": { - "method": "PATCH", - "path": "/posts/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deletePost": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/posts/{{.args.id}}" - } - }, - "createComment": { - "type": "Comment", - "args": { - "input": { "type": "CommentInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/comments", - "body": "{{.args.input}}" - } - }, - "updateComment": { - "type": "Comment", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "CommentInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/comments/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deleteComment": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/comments/{{.args.id}}" - } - }, - "createAlbum": { - "type": "Album", - "args": { - "input": { "type": "AlbumInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/albums", - "body": "{{.args.input}}" - } - }, - "updateAlbum": { - "type": "Album", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "AlbumInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/albums/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deleteAlbum": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/albums/{{.args.id}}" - } - }, - "createPhoto": { - "type": "Photo", - "args": { - "input": { "type": "PhotoInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/photos", - "body": "{{.args.input}}" - } - }, - "updatePhoto": { - "type": "Photo", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "PhotoInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/photos/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deletePhoto": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/photos/{{.args.id}}" - } - }, - "createTodo": { - "type": "Todo", - "args": { - "input": { "type": "TodoInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/todos", - "body": "{{.args.input}}" - } - }, - "updateTodo": { - "type": "Todo", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "TodoInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/todos/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deleteTodo": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/todos/{{.args.id}}" - } - }, - "createUser": { - "type": "User", - "args": { - "input": { "type": "UserInput", "required": true } - }, - "http": { - "method": "POST", - "path": "/users", - "body": "{{.args.input}}" - } - }, - "updateUser": { - "type": "User", - "args": { - "id": { "type": "Int", "required": true }, - "input": { "type": "UserInput", "required": true } - }, - "http": { - "method": "PUT", - "path": "/users/{{.args.id}}", - "body": "{{.args.input}}" - } - }, - "deleteUser": { - "type": "Boolean", - "args": { - "id": { "type": "Int", "required": true } - }, - "http": { - "method": "DELETE", - "path": "/users/{{.args.id}}" - } - } - } - }, - "Post": { - "fields": { - "id": { "type": "Int", "required": true }, - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "body": { "type": "String", "required": true }, - "user": { - "type": "User", - "call": { - "steps": [ - { "query": "user", "args": { "id": "{{.value.userId}}" } } - ] - } - }, - "comments": { - "type": "Comment", - "list": true, - "call": { - "steps": [ - { - "query": "comments", - "args": { "post": "{{.value.id}}" } - } - ] - } - } - } - }, - "Comment": { - "fields": { - "id": { "type": "Int", "required": true }, - "postId": { "type": "Int", "required": true }, - "name": { "type": "String", "required": true }, - "email": { "type": "String", "required": true }, - "body": { "type": "String", "required": true }, - "post": { - "type": "Post", - "call": { - "steps": [ - { "query": "post", "args": { "id": "{{.value.postId}}" } } - ] - } - } - } - }, - "Album": { - "fields": { - "id": { "type": "Int", "required": true }, - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "user": { - "type": "User", - "call": { - "steps": [ - { "query": "user", "args": { "id": "{{.value.userId}}" } } - ] - } - }, - "photos": { - "type": "Photo", - "list": true, - "call": { - "steps": [ - { - "query": "photos", - "args": { "album": "{{.value.id}}" } - } - ] - } - } - } - }, - "Photo": { - "fields": { - "id": { "type": "Int", "required": true }, - "albumId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "url": { "type": "String", "required": true }, - "thumbnailUrl": { "type": "String", "required": true }, - "album": { - "type": "Album", - "call": { - "steps": [ - { "query": "album", "args": { "id": "{{.value.albumId}}" } } - ] - } - } - } - }, - "Todo": { - "fields": { - "id": { "type": "Int", "required": true }, - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "completed": { "type": "Boolean", "required": true }, - "user": { - "type": "User", - "call": { - "steps": [ - { "query": "user", "args": { "id": "{{.value.userId}}" } } - ] - } - } - } - }, - "User": { - "fields": { - "id": { "type": "Int", "required": true }, - "name": { "type": "String", "required": true }, - "username": { "type": "String", "required": true }, - "email": { "type": "String", "required": true }, - "address": { "type": "Address" }, - "phone": { "type": "String" }, - "website": { "type": "String" }, - "company": { "type": "Company" }, - "posts": { - "type": "Post", - "list": true, - "call": { - "steps": [ - { - "query": "posts", - "args": { "user": "{{.value.id}}" } - } - ] - } - }, - "albums": { - "type": "Album", - "list": true, - "call": { - "steps": [ - { - "query": "albums", - "args": { "user": "{{.value.id}}" } - } - ] - } - }, - "todos": { - "type": "Todo", - "list": true, - "call": { - "steps": [ - { - "query": "todos", - "args": { "user": "{{.value.id}}" } - } - ] - } - } - } - }, - "Address": { - "fields": { - "street": { "type": "String" }, - "suite": { "type": "String" }, - "city": { "type": "String" }, - "zipcode": { "type": "String" }, - "geo": { "type": "Geo" } - } - }, - "Geo": { - "fields": { - "lat": { "type": "String" }, - "lng": { "type": "String" } - } - }, - "Company": { - "fields": { - "name": { "type": "String" }, - "catchPhrase": { "type": "String" }, - "bs": { "type": "String" } - } - }, - "PostInput": { - "fields": { - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "body": { "type": "String", "required": true } - } - }, - "PartialPostInput": { - "fields": { - "userId": { "type": "Int" }, - "title": { "type": "String" }, - "body": { "type": "String" } - } - }, - "CommentInput": { - "fields": { - "postId": { "type": "Int", "required": true }, - "name": { "type": "String", "required": true }, - "email": { "type": "String", "required": true }, - "body": { "type": "String", "required": true } - } - }, - "AlbumInput": { - "fields": { - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true } - } - }, - "PhotoInput": { - "fields": { - "albumId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "url": { "type": "String", "required": true }, - "thumbnailUrl": { "type": "String", "required": true } - } - }, - "TodoInput": { - "fields": { - "userId": { "type": "Int", "required": true }, - "title": { "type": "String", "required": true }, - "completed": { "type": "Boolean", "required": true } - } - }, - "UserInput": { - "fields": { - "name": { "type": "String", "required": true }, - "username": { "type": "String", "required": true }, - "email": { "type": "String", "required": true }, - "address": { "type": "AddressInput" }, - "phone": { "type": "String" }, - "website": { "type": "String" }, - "company": { "type": "CompanyInput" } - } - }, - "AddressInput": { - "fields": { - "street": { "type": "String" }, - "suite": { "type": "String" }, - "city": { "type": "String" }, - "zipcode": { "type": "String" }, - "geo": { "type": "GeoInput" } - } - }, - "GeoInput": { - "fields": { - "lat": { "type": "String" }, - "lng": { "type": "String" } - } - }, - "CompanyInput": { - "fields": { - "name": { "type": "String" }, - "catchPhrase": { "type": "String" }, - "bs": { "type": "String" } - } - }, - "ComplexQuery": { - "fields": { - "userWithPostsAndComments": { - "type": "UserWithPostsAndComments", - "args": { - "userId": { "type": "Int", "required": true } - }, - "call": { - "steps": [ - { "query": "user", "args": { "id": "{{.args.userId}}" } }, - { - "query": "posts", - "args": { "user": "{{.value.id}}" } - }, - { - "query": "comments", - "args": { "post": "{{.value.posts.0.id}}" } - } - ] - } - } - } - }, - "UserWithPostsAndComments": { - "fields": { - "user": { "type": "User" }, - "posts": { "type": "Post", "list": true }, - "comments": { "type": "Comment", "list": true } - } - } - } -} +```bash +curl -O https://raw.githubusercontent.com/tailcallhq/tailcall/main/examples/jsonplaceholder.json ``` +:::note +The @call directive is used to chain multiple queries together. +It allows the result of one query to be used as an argument for another query. +For example, in the Post type, the @call directive fetches the user who created the post +by using the userId from the post as an argument for the user query. +The steps attribute is an array of query steps that are executed sequentially. +Each step specifies a query to be called and the arguments to be passed to it. +The `{{.value}}` placeholder is used to reference the result of the previous step in the chain. +::: + + The above file is a standard `.graphQL` file, with some minor additions such as `@upstream` and `@http` directives. Basically we specify the GraphQL schema and how to resolve that GraphQL schema in the same file, without having to write any code! ## Starting the GraphQL server