Skip to content

Commit ef48f24

Browse files
committed
initial commit
0 parents  commit ef48f24

File tree

7 files changed

+410
-0
lines changed

7 files changed

+410
-0
lines changed

.devcontainer/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine/.devcontainer/base.Dockerfile
2+
3+
# [Choice] Alpine version: 3.16, 3.15, 3.14, 3.13
4+
ARG VARIANT="3.16"
5+
FROM mcr.microsoft.com/vscode/devcontainers/base:0-alpine-${VARIANT}
6+
7+
RUN apk add python3 py3-pip

.devcontainer/devcontainer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2+
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine
3+
{
4+
"name": "Alpine",
5+
"build": {
6+
"dockerfile": "Dockerfile",
7+
// Update 'VARIANT' to pick an Alpine version: 3.13, 3.14, 3.15, 3.16
8+
"args": { "VARIANT": "3.16" }
9+
},
10+
11+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
12+
// "forwardPorts": [],
13+
14+
// Use 'postCreateCommand' to run commands after the container is created.
15+
// "postCreateCommand": "uname -a",
16+
17+
// Replace when using a ptrace-based debugger like C++, Go, and Rust
18+
// "runArgs": [ "--init", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
19+
20+
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
21+
"remoteUser": "vscode"
22+
}

.github/workflows/publish.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Publish docs via GitHub Pages
2+
on:
3+
push:
4+
branches:
5+
- main
6+
jobs:
7+
build:
8+
name: Deploy docs
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout main
12+
uses: actions/checkout@v2
13+
- name: Deploy docs
14+
uses: mhausenblas/mkdocs-deploy-gh-pages@master
15+
env:
16+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17+
CONFIG_FILE: mkdocs.yaml
18+
EXTRA_PACKAGES: build-base
19+
REQUIREMENTS: requirements.txt

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Docs
2+
3+
```
4+
pip install -r requirements.txt
5+
mkdocs serve
6+
```

docs/index.md

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
![Logo](https://raw.githubusercontent.com/graphql-crystal/graphql/main/assets/logo.svg)
2+
3+
GraphQL server library for Crystal.
4+
5+
- **Boilerplate-free**: Schema generated at compile time
6+
- **Type-safe**: Crystal guarantees your code matches your schema
7+
- **High performance**: See [benchmarks](https://github.com/graphql-crystal/benchmarks)
8+
9+
## Getting Started
10+
11+
Install the shard by adding the following to our `shard.yml`:
12+
13+
```yaml
14+
dependencies:
15+
graphql:
16+
github: graphql-crystal/graphql
17+
```
18+
19+
Then run `shards install`.
20+
21+
The first step is to define a query object. This is the root type for all
22+
queries and it looks like this:
23+
24+
```crystal
25+
require "graphql"
26+
27+
@[GraphQL::Object]
28+
class Query < GraphQL::BaseQuery
29+
@[GraphQL::Field]
30+
def hello(name : String) : String
31+
"Hello, #{name}!"
32+
end
33+
end
34+
```
35+
36+
Now we can create a schema object:
37+
38+
```crystal
39+
schema = GraphQL::Schema.new(Query.new)
40+
```
41+
42+
To verify we did everything correctly, we can print out the schema:
43+
44+
```crystal
45+
puts schema.document.to_s
46+
```
47+
48+
Which, among several built-in types, prints our query type:
49+
50+
```graphql
51+
type Query {
52+
hello(name: String!): String!
53+
}
54+
```
55+
56+
To serve our API over HTTP we call `schema.execute` with the request parameters and receive a JSON string. Here is an example for Kemal:
57+
58+
```crystal
59+
post "/graphql" do |env|
60+
env.response.content_type = "application/json"
61+
62+
query = env.params.json["query"].as(String)
63+
variables = env.params.json["variables"]?.as(Hash(String, JSON::Any)?)
64+
operation_name = env.params.json["operationName"]?.as(String?)
65+
66+
schema.execute(query, variables, operation_name)
67+
end
68+
```
69+
70+
Now we're ready to query our API:
71+
72+
```bash
73+
curl \
74+
-X POST \
75+
-H "Content-Type: application/json" \
76+
--data '{ "query": "{ hello(name: \"John Doe\") }" }' \
77+
http://0.0.0.0:3000/graphql
78+
```
79+
80+
This should return:
81+
82+
```json
83+
{ "data": { "hello": "Hello, John Doe!" } }
84+
```
85+
86+
For easier development, we recommend using [GraphiQL](https://github.com/graphql/graphiql).
87+
A starter template combining Kemal and GraphiQL is found at [examples/graphiql](examples/graphiql).
88+
89+
## Context
90+
91+
`context` is a optional argument that our fields can retrieve. It lets fields
92+
access global data, like database connections.
93+
94+
```crystal
95+
# Define our own context type
96+
class MyContext < GraphQL::Context
97+
@pi : Float64
98+
def initialize(@pi)
99+
end
100+
end
101+
102+
# Pass it to schema.execute
103+
context = MyContext.new(Math::PI)
104+
schema.execute(query, variables, operation_name, context)
105+
106+
# Access it in our fields
107+
@[GraphQL::Object]
108+
class MyMath < GraphQL::BaseObject
109+
@[GraphQL::Field]
110+
def pi(context : MyContext) : Float64
111+
context.pi
112+
end
113+
end
114+
```
115+
116+
Context instances must not be reused for multiple executions.
117+
118+
## Objects
119+
120+
Objects are perhaps the most commonly used type in GraphQL. They are implemented
121+
as classes. To define a object, we need a `GraphQL::Object` annotation and to inherit
122+
`GraphQL::BaseObject`. Fields are methods with a `GraphQL::Field` annotation.
123+
124+
```crystal
125+
@[GraphQL::Object]
126+
class Foo < GraphQL::BaseObject
127+
# type restrictions are mandatory on fields
128+
@[GraphQL::Field]
129+
def hello(first_name : String, last_name : String) : String
130+
"Hello #{first_name} #{last_name}"
131+
end
132+
133+
# besides basic types, we can also return other objects
134+
@[GraphQL::Field]
135+
def bar : Bar
136+
Bar.new
137+
end
138+
end
139+
140+
@[GraphQL::Object]
141+
class Bar < GraphQL::BaseObject
142+
@[GraphQL::Field]
143+
def baz : Float64
144+
42_f64
145+
end
146+
end
147+
```
148+
149+
For simple objects, we can use instance variables:
150+
151+
```crystal
152+
@[GraphQL::Object]
153+
class Foo < GraphQL::BaseObject
154+
@[GraphQL::Field]
155+
property bar : String
156+
157+
@[GraphQL::Field]
158+
getter baz : Float64
159+
end
160+
```
161+
162+
## Query
163+
164+
Query is the root type of all queries.
165+
166+
```crystal
167+
@[GraphQL::Object]
168+
class Query < GraphQL::BaseQuery
169+
@[GraphQL::Field]
170+
def echo(str : String) : String
171+
str
172+
end
173+
end
174+
175+
schema = GraphQL::Schema.new(Query.new)
176+
```
177+
178+
## Mutation
179+
180+
Mutation is the root type for all mutations.
181+
182+
```crystal
183+
@[GraphQL::Object]
184+
class Mutation < GraphQL::BaseMutation
185+
@[GraphQL::Field]
186+
def echo(str : String) : String
187+
str
188+
end
189+
end
190+
191+
schema = GraphQL::Schema.new(Query.new, Mutation.new)
192+
```
193+
194+
## Input Objects
195+
196+
Input objects are objects that are used as field arguments. To define an input
197+
object, use a `GraphQL::InputObject` annotation and inherit `GraphQL::BaseInputObject`.
198+
It must define a constructor with a `GraphQL::Field` annotation.
199+
200+
```crystal
201+
@[GraphQL::InputObject]
202+
class User < GraphQL::BaseInputObject
203+
getter first_name : String?
204+
getter last_name : String?
205+
206+
@[GraphQL::Field]
207+
def initialize(@first_name : String?, @last_name : String?)
208+
end
209+
end
210+
```
211+
212+
## Enums
213+
214+
Defining enums is straightforward. Just add a `GraphQL::Enum` annotation:
215+
216+
```crystal
217+
@[GraphQL::Enum]
218+
enum IPAddressType
219+
IPv4
220+
IPv6
221+
end
222+
```
223+
224+
## Scalars
225+
226+
The following scalar values are supported:
227+
228+
- `Int32` <-> `Int`
229+
- `Float64` <-> `Float`
230+
- `String` <-> `String`
231+
- `Bool` <-> `Boolean`
232+
- `GraphQL::Scalars::ID` <-> `String`
233+
234+
Built-in custom scalars:
235+
236+
- `GraphQL::Scalars::BigInt` <-> `String`
237+
238+
Custom scalars are created by implementing from_json/to_json:
239+
240+
```crystal
241+
@[GraphQL::Scalar]
242+
class ReverseStringScalar < GraphQL::BaseScalar
243+
@value : String
244+
245+
def initialize(@value)
246+
end
247+
248+
def self.from_json(string_or_io)
249+
self.new(String.from_json(string_or_io).reverse)
250+
end
251+
252+
def to_json(builder : JSON::Builder)
253+
builder.scalar(@value.reverse)
254+
end
255+
end
256+
```
257+
258+
## Interfaces
259+
260+
Interfaces are not supported.
261+
262+
## Subscriptions
263+
264+
Subscriptions are not supported.
265+
266+
## Annotation Arguments
267+
268+
### name
269+
270+
Supported on: `Object`, `InputObject`, `Field`, `Enum`, `Scalar`
271+
272+
We can use the `name` argument to customize the introspection type name of a
273+
type. This is not needed in most situations because type names are automatically
274+
converted to PascalCase or camelCase. However, `item_id` converts to
275+
`itemId`, but we might want to use `itemID`. For this, we can use the `name`
276+
argument.
277+
278+
```crystal
279+
@[GraphQL::Object(name: "Sheep")]
280+
class Wolf
281+
@[GraphQL::Field(name: "baa")]
282+
def howl : String
283+
"baa"
284+
end
285+
end
286+
```
287+
288+
### description
289+
290+
Supported on: `Object`, `InputObject`, `Field`, `Enum`, `Scalar`
291+
292+
Describes the type. Descriptions are available through the introspection interface
293+
so it's always a good idea to set this argument.
294+
295+
```crystal
296+
@[GraphQL::Object(description: "I'm a sheep, I promise!")]
297+
class Wolf
298+
end
299+
```
300+
301+
### deprecated
302+
303+
Supported on: `Field`
304+
305+
The deprecated argument marks a type as deprecated.
306+
307+
```crystal
308+
class Sheep
309+
@[GraphQL::Field(deprecated: "This was a bad idea.")]
310+
def fight_wolf : String
311+
"Wolf ate sheep"
312+
end
313+
end
314+
```
315+
316+
### arguments
317+
318+
Sets names and descriptions for field arguments. Note that
319+
arguments cannot be marked as deprecated.
320+
321+
```crystal
322+
class Sheep
323+
@[GraphQL::Field(arguments: {weapon: {name: "weaponName", description: "The weapon the sheep should use."}})]
324+
def fight_wolf(weapon : String) : String
325+
if weapon == "Atomic Bomb"
326+
"Sheep killed wolf"
327+
else
328+
"Wolf ate sheep"
329+
end
330+
end
331+
end
332+
```
333+
334+
## Field Arguments
335+
336+
Field arguments are automatically resolved. A type with a default value becomes
337+
optional. A nilable type is also considered a optional type.

0 commit comments

Comments
 (0)