33 | Utilize the input fields on the left to make a GraphQL query or mutation.
34 |
35 |
36 |
37 |
38 |
39 |
Response
40 |
41 |
45 | Moments after the query is excecuted, the raw response from the
46 | GraphQL API is displayed.
47 |
48 |
49 |
50 |
51 |
Cache
52 |
53 |
57 | Finally, we can see our destructured query and responses which are
58 | currently stored in a Redis cache. If you query for a specific
59 | property that is stored in the cache, the Obsidian algorithm will
60 | find and return it. Eliminating the need to query the database again.
61 |
8 | Some applications require regular streams of GraphQL queries sent via
9 | HTTP requests. If you find yourself in this position,
10 | obsidian can help. The
11 | query method accepts an
12 | optional 'pollInterval' property in its options parameter, which
13 | provides near-real-time synchronization with your server by causing a
14 | query to execute periodically and will automatically update the cache
15 | consistently. To enable polling for a query, pass a 'pollInterval'
16 | configuration option to the{' '}
17 | query method with a specified
18 | interval in milliseconds:
19 |
20 |
21 | {`function actorsPhoto() {
22 | const interval = query(GET_ACTOR_PHOTO, {
23 | pollInterval: 2000,
24 | });
25 | setInterval(interval); // react hook to add the interval in the state
26 | return (
27 |
28 |
Actor Showcase
29 |
Check out our favorite actor photo by clicking the button below
30 |
37 |
Name: {actor.name}
38 |
39 |
40 | );
41 | }`}
42 |
43 |
44 |
45 | This query will be sent every 2 seconds, updating the cache via the
46 | default normalized caching strategy if any changes are found. You can
47 | also stop polling dynamically with the{' '}
48 | stopPollInterval method on the
49 | cache class using the interval returned by the invocation of the{' '}
50 | obsidian query, here an example:
51 |
8 | This chapter will cover the most common errors related to{' '}
9 | obsidian implementation.
10 |
11 |
ObsidianRouter
12 |
Connection Refused
13 |
14 | This error is likely to arise if you have not disabled the server-side
15 | cache and/or do not have an active redis instance at port 6379. To learn
16 | more about redis and server-side caching, check out the{' '}
17 | props.setDocsPage('Server')}>
18 | Caching
19 | {' '}
20 | section.
21 |
22 |
ObsidianWrapper
23 |
Cache is returning undefined
24 |
25 | Check to see if your query and/or mutation is being sent with an id
26 | field, as not providing one prevents normalization of the response
27 | object
28 |
29 |
Your app doesn't work after wrapping it with Obsidian
30 |
31 | Check to make sure you've wrapped your app at the proper level. You
32 | should be wrapping your app at the top-level component, but not in the
33 | server, as this will require wrapping the app in the hydrate method as
34 | well. For example, if your top level component is App, App should be
35 | used in the renderToString{' '}
36 | method and in the hydrate{' '}
37 | methods, and ObsidianWrapper should be the first child of App. Check out
38 | the chapter on server-side rendering to see an example.
39 |
40 |
Recap & Next Up
41 |
42 | This section has walked through a simple implementation of{' '}
43 | obsidian with ObsidianRouter and
44 | ObsidianWrapper, and covered the most common use cases and errors. Next,
45 | we're going to dive into caching and{' '}
46 | obsidian's design philosophy and
47 | guiding development principles. Once we have a firm grasp on how{' '}
48 | obsidian approaches GraphQL
49 | caching, we'll examine the specifics of server-side and client-side
50 | caching in obsidian.
51 |
8 | In this chapter we seek to lay bare our approach to GraphQL caching,
9 | providing context for our caching implementations in each part of{' '}
10 | obsidian.
11 |
12 |
Core Values
13 |
Fast
14 |
15 | Caching with obsidian should be nearly imperceptible - a client or
16 | server module should feel no slower with a caching layer
17 |
18 |
19 | While building obsidian's
20 | caching strategies, speed reigned supreme. We believe that a caching
21 | layer for your GraphQL service has to be fast and efficient. Your API
22 | should not take a large performance hit after incorporating a cache.
23 |
24 |
Flexible
25 |
26 | Caching with obsidian should be developer friendly without sacrificing
27 | performance{' '}
28 |
29 |
30 | obsidian's flexible API is
31 | designed to be performant, no matter how big or small your project is.
32 | Our caching solutions do not require strict conventions or a schema on
33 | the client side to be able to offer efficiency and expandability for the
34 | developer.
35 |
36 |
Consistent
37 |
38 | Caching with obsidian should prize consistency - discerning truth should
39 | be automatic and transparent
40 |
41 |
42 | Many GraphQL caching solutions give up on consistency and mutations,
43 | choosing to give the tools to the developer and letting go of the
44 | reigns. With obsidian,
45 | consistent truth is a priority and a responsibility, rather than an
46 | afterthought.
47 |
48 |
Integration
49 |
50 | obsidian was built for Deno, and
51 | as such it has been designed to integrate into React apps the way they
52 | are built in Deno, on both the frontend and backend. While{' '}
53 | obsidian is still in it's early
54 | stages, we are actively seeking to define what that means. For now, Oak
55 | has proven to be Deno's prevailing framework, and React apps are
56 | typically constructed with server-side rendering.
57 |
58 |
59 | We choose to view these restrictions as inspiration for innovation, and
60 | we have applied that philosophy to our caching implementations. By
61 | adhering to pre-defined scaffolding, opportunities to improve upon
62 | existing methodologies readily present themselves. Whenever possible, we
63 | chose to build in favor of the above core values, and we hope they can
64 | guide your implementation of{' '}
65 | obsidian.
66 |
67 |
Recap & Next Up
68 |
69 | Now that we have a grasp on the guiding principles behind the
70 | development of obsidian and it's
71 | integration into your application, we'll move onto a deeper dive into
72 | the caching capabilities of{' '}
73 | obsidian.
74 |
8 | In this chapter, we'll dig into caching in ObsidianRouter, as well as
9 | redis implementation.
10 |
11 |
Server-Side Caching with Obsidian
12 |
13 | Obsidian utilizes a local redis server to store our server-side cache,
14 | keeping our cache fast and responsive while maintaining ACID compliance.
15 | To learn how to setup redis on your machine, check out the{' '}
16 | quick start{' '}
17 | documentation.
18 |
19 |
20 | By default, your redis server should be running at port 6379. If you
21 | need to change your port, you may do so inside the ObsidianRouter
22 | options.
23 |
24 |
ObsidianRouter
25 |
26 | ObsidianRouter setup requires an options object, which we demonstrated
27 | in the Getting Started chapter. The options object accepts the following
28 | properties, only three of which are required:
29 |
30 |
31 |
32 | Router - (required) You must provide the Oak Router
33 | class
34 |
35 |
36 | path - (optional, default:{' '}
37 | '/graphql') Your GraphQL
38 | service will be available at this endpoint
39 |
40 |
41 | typeDefs - (required) Your GraphQL schema goes here
42 |
43 |
44 | resolvers - (required) Your resolvers go here
45 |
46 |
47 | context - (optional) A function to alter your Oak
48 | server's context object upon entering the router
49 |
50 |
51 | usePlayground - (optional, default:{' '}
52 | true) Set to{' '}
53 | false to disable the GraphQL
54 | Playground. Recommended when deploying your application in production
55 |
56 |
57 | useCache - (optional, default:{' '}
58 | true) Set to{' '}
59 | false to disable server-side
60 | caching. This negates the caching layer, and institutes a cacheless
61 | caching strategy in ObsidianRouter
62 |
63 |
64 | redisPort - (optional, default:{' '}
65 | 6379) If your redis server is
66 | running at a different port, specify it here
67 |
68 |
69 | maxQueryDepth - (optional, default:{' '}
70 | 0) If set to integer of 1 or
71 | more, returns error in response to request object with nesting depth
72 | greater than the maxQueryDepth and does not route request to GraphQL
73 | server. Feature is disabled by default.
74 |
75 |
76 |
An example of ObsidianRouter with optional parameters:
93 | This section has walked through caching in{' '}
94 | obsidian, including our approach
95 | to GraphQL caching and the various caching strategies{' '}
96 | obsidian offers.
97 |
34 | endpoint - (default:{' '}
35 | '/graphql') The endpoint where{' '}
36 | obsidian should send all
37 | requests by default
38 |
39 |
40 | cacheRead - (default:{' '}
41 | true) When this option is
42 | enabled, Obsidian will check the cache to see if the relevant data is
43 | available before querying the server
44 |
45 |
46 | cacheWrite - (default:{' '}
47 | true) When this option is
48 | enabled, Obsidian will write responses to the cache
49 |
50 |
51 | pollInterval - (default:{' '}
52 | null) Turns query polling on
53 | and specifies how often Obsidian should send a GraphQL query to the
54 | server
55 |
80 | endpoint - (default:{' '}
81 | '/graphql') The endpoint where{' '}
82 | obsidian should send this
83 | request
84 |
85 |
86 | cache - (default:{' '}
87 | true) When this option is
88 | enabled, obsidian will
89 | automatically update the cache after a mutation.
90 |
91 |
92 | toDelete - (default:{' '}
93 | false) When this option is set
94 | to true, it will indicate to
95 | obsidian that a delete
96 | mutation is being sent and to update the value of that hash in the
97 | cache with 'DELETED'.
98 |
108 | In this chapter we learned how to control our caching strategy in the{' '}
109 | obsidian client,
110 | ObsidianWrapper. Next, we'll dive into caching options in
111 | ObsidianRouter.
112 |
8 | obsidian is a{' '}
9 | Deno{' '}
10 | GraphQL server module and a GraphQL
11 | client, built to optimize application performance via caching
12 | strategies. obsidian's server
13 | module may be used independently to quickly build out a GraphQL API
14 | while enabling the full suite of caching strategies that{' '}
15 | obsidian offers.
16 |
17 |
18 | obsidian simplifies GraphQL
19 | implementations in Deno by offering a solution integrated across the
20 | stack, ensuring your API stays lean and performant even as it's scope
21 | grows. By utilizing obsidian and
22 | the power of server-side rendering, we can maintain rapid page-load
23 | times.
24 |
25 |
The Module
26 |
27 | obsidian is a Deno module,
28 | published at deno.land.
29 | There are two distinct parts to{' '}
30 | obsidian: ObsidianRouter, a
31 | caching GraphQL router built upon Deno's{' '}
32 | Oak server framework, and
33 | ObsidianWrapper, a React component
34 | that functions as a GraphQL client for your application, providing
35 | global access to fetching and caching capabilities.
36 |
37 |
38 | It is important to note that ObsidianRouter can be implemented as a
39 | standalone GraphQL router for your server, and third-party client
40 | implementations may be used in your client-side code. ObsidianWrapper
41 | can also be used independently of ObsidianRouter if normalized caching
42 | capabilities are not needed or desired. However, ObsidianWrapper's
43 | normalized caching strategy is only available when used in conjunction
44 | with ObsidianRouter- this choice was made to preserve the destructuring
45 | and normalizing algorithm that is at the heart of{' '}
46 | obsidian's caching strategy.
47 |
48 |
49 | This two-pronged approach enables{' '}
50 | obsidian to guide your GraphQL
51 | implementation every step of the way, and brings clarity and speed to
52 | every part of your app.
53 |
54 |
55 | NOTE - To keep our server-side cache efficient and ACID
56 | compliant, obsidian uses redis
57 | to store cached data. If you are not utilizing the caching features of
58 | ObsidianRouter, you do not need to have a running redis server.
59 |
60 |
The Documentation
61 |
We've split our documentation into four distinct sections.
62 |
63 |
64 | Basics guides us through a simple implementation of
65 | full stack obsidian, detailing
66 | the setup of ObsidianRouter, ObsidianWrapper, usage patterns in React,
67 | and how to harness server-side rendering to improve your client
68 | caching strategy.
69 |
70 |
71 | Philosophy provides a high-level overview of caching
72 | in obsidian, revealing our
73 | vision of obsidian integration
74 | and guiding principles for development.
75 |
76 |
77 | Caching covers the different caching strategies{' '}
78 | obsidian offers and draws upon
79 | our Philosophy to illustrate their implementations in ObsidianRouter
80 | and ObsidianWrapper.
81 |
82 |
83 | Advanced contains guides for more specialized options
84 | and use-cases that we won't need while first exploring{' '}
85 | obsidian.
86 |
87 |
88 |
89 | We hope you enjoy working with{' '}
90 | obsidian!
91 |
8 | obsidian is Deno's first native
9 | GraphQL caching client and server module. Boasting lightning-fast
10 | caching and fetching capabilities alongside headlining normalization and
11 | destructuring strategies,{' '}
12 | obsidian is equipped to support
13 | scalable, highly performant applications.
14 |
15 |
16 | Optimized for use in server-side rendered React apps built with Deno,
17 | full stack integration of{' '}
18 | obsidian enables many of its
19 | most powerful features, including optimized caching exchanges between
20 | client and server and extremely lightweight client-side caching.
21 |
8 | In this chapter, we'll learn how to send GraphQL mutations using{' '}
9 | mutate to ensure our cache
10 | behaves as expected.
11 |
12 |
mutate
13 |
14 | To maintain and ascertain the truth of our cache, GraphQL mutations must
15 | be handled differently than queries. By allowing the user to send a
16 | configurations options object with
17 | mutate, the cache can be altered
18 | to their liking. To learn more about mutations, caching, and{' '}
19 | obsidian's caching philosophy,
20 | see the{' '}
21 | props.setDocsPage('Philosophy')}>
22 | Caching
23 | {' '}
24 | section.
25 |
26 |
27 | Just as with query,{' '}
28 | mutate is made available via{' '}
29 | useObsidian. To send a mutation,
30 | first destructure the hook:
31 |
110 | We don’t need an options object since we are using the default settings.
111 |
112 |
113 |
114 | To send a delete mutation request, provide an options object with delete
115 | set to true. This will let
116 | obsidian know to update value in
117 | the cache to ‘DELETED’
118 |
145 | When sending a mutation request to create a new element, provide an
146 | update function to let obsidian
147 | know how to store the newly created element into the existing cache.
148 |
190 | In this chapter we covered how to send mutations using{' '}
191 | mutate. To round out the Basics
192 | section, we'll examine some common errors you might find when using{' '}
193 | obsidian.
194 |
8 | In this section, we cover the details of caching in{' '}
9 | obsidian. In this chapter, we'll
10 | learn more about the different caching strategies{' '}
11 | obsidian makes available to us,
12 | and their pros and cons.
13 |
14 |
Caching in Obsidian
15 |
16 | What do we mean when we talk about caching strategies? Throughout
17 | this documentation, we use the term to refer to the methods by which you
18 | cache and store your GraphQL data. These methods each come with their
19 | own strengths and weaknesses- just like in a game of chess, where a
20 | development strategy may yield long-term benefits but come at the cost
21 | of short-term disadvantages.{' '}
22 | obsidian's caching strategies
23 | offer control over how ObsidianRouter and ObsidianWrapper store and
24 | reconstruct your data, so that you can pick the strategy that best suits
25 | your GraphQL needs.
26 |
33 | At first blush the cacheless caching strategy sounds like an oxymoron-
34 | and it is. However, we feel it is important to denote cacheless as it's
35 | own caching strategy, as{' '}
36 | obsidian does not demand a
37 | caching layer. If you would prefer to forgo another caching strategy,{' '}
38 | obsidian can be configured to
39 | bypass the caching layer and serve as a basic GraphQL router or client.
40 |
41 |
42 | The cacheless strategy is recommended when your GraphQL needs are light.
43 | If you make a very small number of content calls to a GraphQL service,
44 | caching can be an unnecessary complexity.
45 |
46 |
Whole-Query Caching
47 |
48 | Whole-query caching is a simple and powerful caching strategy.
49 | Whole-query caching stores each query and response in the caching layer.
50 | If an identical query enters{' '}
51 | obsidian, the stored response is
52 | returned in its entirety. This approach to caching is extremely fast, as
53 | long as queries are absolutely identical. However, even minor
54 | differences in queries will store new key-value pairs in the cache,
55 | often resulting in large amounts of redundant information.
56 |
57 |
58 | Whole-query caching is recommended if your app makes many identical
59 | GraphQL queries throughout a typical user-session, such as flipping
60 | through pages of content.
61 |
62 |
Destructuring & Normalization
63 |
64 | Normalized caching is obsidian's
65 | headlining caching strategy. As responses from your GraphQL queries exit{' '}
66 | obsidian, they are{' '}
67 | normalized and stored in the cache at an object level. Then, by
68 | parsing incoming queries and destructuring their contents,{' '}
69 | obsidian is able to reconstruct
70 | a response object from the cache. This removes replication of data in
71 | the cache, improves memory management, and opens the door to mutation
72 | consistency in the cache layer.
73 |
74 |
75 | To facilitate the normalized caching strategy,{' '}
76 | obsidian requires your responses
77 | to always return IDs coupled with each GraphQL object type. What exactly
78 | does that mean? We can learn more by looking at an example:
79 |
90 | This is a GraphQL object type- it's comprised of many fields, each of
91 | which can be assigned a Scalar value (Int, Float, String, Boolean, or
92 | ID) or another object type. For example, our Movie type could also have
93 | a field comprised of an array of actors that appear in the film:
94 |
114 | Note that for both object types, we have an id field. To utilize the
115 | normalized caching strategy in{' '}
116 | obsidian, you must request the
117 | id field of each object type you receive in every query. For example, if
118 | you'd like to query for just the actors in a particular movie, your
119 | query should look like this:
120 |
135 | We requested the id of both the Movie object type and the Actor object
136 | type. Conversely, if you don't need any information about the actors in
137 | a movie, there's no need to ask for their id as you won't be accessing
138 | any info found on the Actor object type:
139 |
150 | Normalized caching is incredibly robust, enabling{' '}
151 | obsidian to maintain consistency
152 | with greater precision, and thus ships as the default option when
153 | utilizing ObsidianRouter and ObsidianWrapper.
154 |
155 |
156 | Normalized caching is recommended for more complex and robust GraphQL
157 | applications. If you app is often making queries for subsets of
158 | information on object types or making many similar but unique API calls,
159 | normalized caching can drastically improve load times and help maintain
160 | global consistency.
161 |
162 |
Recap & Next Up
163 |
164 | We've learned about the different caching strategies{' '}
165 | obsidian offers, and highlighted
166 | the best use-case for each. Next, we will walk through the
167 | implementations of each of these caching strategies in both
168 | ObsidianWrapper and ObsidianRouter.
169 |
8 | In this chapter, we'll cover how to fetch GraphQL data in React using
9 | obsidian query, and discuss the
10 | options available to you when querying.
11 |
12 |
useObsidian
13 |
14 | After setting up ObsidianWrapper,
15 | obsidian exposes a custom React
16 | hook, useObsidian, that can be
17 | used to globally access fetching and caching capabilities. Destructure
18 | the hook inside your React components to access
19 | obsidian functionality:
20 |
34 | To run a simple query within a React component, call
35 | obsidian query and pass it a
36 | GraphQL query string as a required first parameter. When your component
37 | renders, the obsidian query
38 | returns a response object from Obsidian Client that contains data you
39 | can use to render your UI. Let's look at an example:
40 |
65 | Whenever Obsidian Client fetches query results from your server, it
66 | automatically caches those results locally. This makes subsequent
67 | executions of the same query extremely fast. To utilize the caching
68 | capabilities of obsidian and to have more control over how the data is
69 | fetched, we’re providing some configuration options as a second and
70 | optional parameter to the
71 | obsidian query in addition to
72 | the query string:
73 |
74 |
75 |
76 | query - Your GraphQL query string.
77 |
78 |
79 | options - (optional) An object with further
80 | parameters.
81 |
82 |
83 | endpoint - (default:
84 | '/graphql') The endpoint
85 | where obsidian should send
86 | this request.
87 |
88 |
89 | cacheRead - (default:
90 | true) Determines whether
91 | the cache should be checked before making a server request.
92 |
93 |
94 | cacheWrite - (default:
95 | true) Determines whether
96 | the response from a server request should be written into the
97 | cache. See the{' '}
98 | props.setDocsPage('Client')}>
99 | Caching
100 | {' '}
101 | section for more details.
102 |
129 | As you can see, invoking{' '}
130 | obsidian query with a query
131 | string as its only argument will make a request to your '/graphql'
132 | endpoint only once, utilizing the destructure caching strategy and
133 | storing the cached data in global memory.
134 |
135 |
136 | We'll explore caching in more detail in the
137 | props.setDocsPage('Strategies')}>
138 | {' '}
139 | Caching
140 | {' '}
141 | section. For now, let's use
142 | obsidian query to showcase a
143 | simple GraphQL request that responds just like the fetch API, returning
144 | a promise that can be consumed with a .then():
145 |
175 | That's it! Subsequent queries will utilize the cache to reconstruct the
176 | result without querying the GraphQL endpoint.
177 |
178 |
Recap & Next Up
179 |
180 | In this chapter we covered{' '}
181 | useObsidian and the basic use
182 | cases of the query method. Next
183 | up, we'll cover mutations and the
184 | mutate method
185 | obsidian supplies.
186 |
8 | In this section, we'll learn{' '}
9 | obsidian by walking through the
10 | setup of a simple full stack server-side rendered React app in Deno.
11 |
12 |
ObsidianRouter
13 |
14 | We're going to build the backend of our app with{' '}
15 |
16 | Oak
17 |
18 | , a middleware framework for your Deno server. ObsidianRouter is an{' '}
19 | Oak router, so we must build our server with Oak in order to use{' '}
20 | obsidian.
21 |
22 |
Installation
23 |
24 | Thanks to Deno's ECMAScript package importing, installation of Oak and{' '}
25 | obsidian is incredibly simple.
26 | Just import the pieces of the modules you need at the top of your
27 | server, like so:
28 |
36 | NOTE - Throughout these guides, we will be illustrating imports
37 | directly from a url. It is common practice for Deno apps to utilize a
38 | dependencies file, usually called{' '}
39 | deps.ts, where packages are
40 | imported from their urls and then referenced with local imports
41 | throughout the app. We recommend this approach, with the key caveat that
42 | your Oak import statements not be accidentally bundled with your
43 | client-side code, as the browser is unable to interpret any references
44 | to Deno. You can easily accomplish this by creating two separate
45 | dependency files for your server and client code.
46 |
47 |
Oak
48 |
49 | Now that we've imported our modules, let's begin by setting up our Oak
50 | server:
51 |
67 | Next we'll add our GraphQL endpoint with ObsidianRouter. Like every
68 | GraphQL server, ObsidianRouter requires a GraphQL schema to define the
69 | structure of our data. obsidian{' '}
70 | provides gql, a{' '}
71 |
72 | tagged template literal
73 | {' '}
74 | that allows ObsidianRouter to read your GraphQL schema. Let's construct
75 | our schema now:
76 |
77 |
78 | {`// server.tsx
79 | const types = (gql as any)\`
80 | type Movie {
81 | id: ID
82 | title: String
83 | releaseYear: Int
84 | }
85 |
86 | type Query {
87 | getMovie: Movie
88 | }
89 | \`;`}
90 |
91 |
92 |
Resolvers
93 |
94 | Great, we have a schema! But in order for ObsidianRouter to do something
95 | with your schema, we need to give it resolvers. Resolvers tell
96 | ObsidianRouter what to do with incoming queries and mutations. Let's
97 | create a resolver for our{' '}
98 | getMovie query:
99 |
116 | NOTE - Resolvers typically do not return hardcoded data like we
117 | have here. Your resolvers can fetch data from anywhere you might
118 | normally fetch data from, like a database or another API, but for the
119 | sake of simplicity our example includes a hardcoded response.
120 |
121 |
ObsidianRouter Setup
122 |
123 | We now have everything we need to create our GraphQL endpoint using
124 | ObsidianRouter. For now, we'll set{' '}
125 | useCache to{' '}
126 | false- we'll learn more about
127 | caching with ObsidianRouter{' '}
128 | props.setDocsPage('Server')}>
129 | later
130 |
131 | . Note that the router should come before your{' '}
132 | app.listen.
133 |
151 | NOTE - If you are building your server in TypeScript, as we are
152 | here, you will have to extend the Oak Router interface to create the
153 | ObsidianRouter. By exposing the{' '}
154 | obsidianSchema property on the
155 | ObsidianRouter, we open the door to a streamlined caching implementation
156 | for your client-side code, which we'll explore in{' '}
157 | props.setDocsPage('ServerSideRendering')}>
158 | server-side rendering
159 |
160 | .
161 |
162 |
Spin Up the Server
163 |
164 | Let's start the server! As Deno requires us to explicitly give
165 | permissions, our command to start up the server looks like this:
166 |
174 | To test our new GraphQL endpoint, head to{' '}
175 | localhost:8000/graphql to test your
176 | queries with GraphQL Playground. To learn more about this tool, check
177 | out their{' '}
178 | GitHub repo.
179 |
180 |
Recap & Next Up
181 |
182 | In this chapter we set up a simple server with Oak and a GraphQL
183 | endpoint with ObsidianRouter. Next, we'll explore ObsidianWrapper,{' '}
184 | obsidian's client
185 | implementation, via a React app built with server-side rendering.
186 |
8 | In this chapter, we'll learn how to implement ObsidianWrapper,{' '}
9 | obsidian's GraphQL client, in a
10 | React app built with server-side rendering.
11 |
12 |
ObsidianWrapper
13 |
14 | Before we can discuss server-side rendering in Deno, we must first build
15 | out our client application. Setting up ObsidianWrapper is super simple:
16 | wrap your app with ObsidianWrapper and you are ready to start using{' '}
17 | obsidian's caching capabilities!{' '}
18 |
19 |
Installation
20 |
21 | Import React and ObsidianWrapper at your top-level component along with
22 | any child components:
23 |
33 | Wrap your main container in ObsidianWrapper. This exposes the{' '}
34 | useObsidian hook, which will
35 | enable us to make GraphQL requests and access our cache from anywhere in
36 | our app.
37 |
94 | At last, let's build our HTML file inside of our{' '}
95 | handlePage function, using
96 | ReactDomServer's renderToString{' '}
97 | method to insert our pre-rendered app inside the body. We'll also send
98 | our initialState object in the head, providing ObsidianWrapper all of
99 | the tools it needs to execute caching on the client-side:
100 |
101 |
102 | {`// server.tsx
103 | import React from 'https://dev.jspm.io/react';
104 | import ReactDomServer from 'https://dev.jspm.io/react-dom/server';
105 | import App from './App.tsx';
106 |
107 | function handlePage(ctx: any) {
108 | try {
109 | const body = (ReactDomServer as any).renderToString();
110 | ctx.response.body = \`
111 |
112 |
113 |
114 | Obsidian Film Showcase
115 |
116 |
117 |
129 | We're almost there! In order to reattach all of our React functionality
130 | to our pre-rendered app, we have to hydrate our root div. First,
131 | let's create the client.tsx file that will contain the hydrate
132 | functionality:
133 |
147 | In the server, we'll use Deno's native emit method to wrap up all of
148 | the React logic contained in our app, ready to be reattached to the DOM
149 | via hydration:
150 |
176 | Just one more step before we're up and running: specify our compiler
177 | options with a tsconfig.json file. To learn more about TypeScript
178 | project configuration, check out the official documentation{' '}
179 |
180 | here
181 |
182 | .
183 |
206 | Our command to start our server has expanded now that we're bundling our
207 | client.tsx file. The new command to start up our server looks like this:
208 |
217 | In this chapter we set up a simple React app and implemented
218 | ObsidianWrapper, enabling fetching and caching at a global level. We
219 | utilized server-side rendering to send a pre-rendered version of our app
220 | to the client. Next, we'll take a look at querying with{' '}
221 | obsidian and the different
222 | methods and options available.
223 |
110 | );
111 | };
112 |
113 | export default ObsidianLogo;
114 |
--------------------------------------------------------------------------------
/client/Components/Team.jsx:
--------------------------------------------------------------------------------
1 | import { React } from '../../deps.ts';
2 | import TeamMember from './TeamMember.jsx';
3 |
4 | const users = [
5 | {
6 | firstName: 'Raymond',
7 | lastName: 'Ahn',
8 | image:
9 | 'https://res.cloudinary.com/os-labs/image/upload/v1625079464/Profile_Pic_tcytta.jpg',
10 | info: 'Raymond is a full-stack software engineer whose focus is on developing scalable and responsive programs through algorithmic optimization. During his spare time, he enjoys exploring new cuisines, game nights, fitness, traveling, and anything music-related.',
11 | linkedin: 'https://www.linkedin.com/in/raymondahn/',
12 | github: 'https://github.com/raymondcodes',
13 | },
14 | {
15 | firstName: 'Kyung',
16 | lastName: 'Lee',
17 | image: 'https://res.cloudinary.com/os-labs/image/upload/v1625091906/IMG_1195_ptmaex.jpg',
18 | info: 'Kyung is a software engineer who loves diving deep into technologies and building robust and efficient web applications. Apart from coding, his hobbies include watching MMA and eating tacos or anything spicy.',
19 | linkedin: 'https://www.linkedin.com/in/kyung-lee-9414a6215/',
20 | github: 'https://github.com/kyunglee1',
21 | },
22 | {
23 | firstName: 'Justin',
24 | lastName: 'McKay',
25 | image:
26 | 'https://res.cloudinary.com/os-labs/image/upload/v1625079448/Screen_Shot_2021-06-28_at_11.24.08_AM_kyfviq.png',
27 | info: 'Justin is a software engineer with a passion for solving complex problems and full stack system design. When not coding he enjoys music theory, fitness, and cold water.',
28 | linkedin: 'https://www.linkedin.com/in/justinwmckay/',
29 | github: 'https://github.com/justinwmckay',
30 | },
31 | {
32 | firstName: 'Cameron',
33 | lastName: 'Simmons',
34 | image:
35 | 'https://res.cloudinary.com/os-labs/image/upload/v1625002810/3C1B71E4-CD2A-4D11-BBF0-B054E80BC132_1_201_a_afrjry.jpg',
36 | info: 'Cameron is a full-stack software engineer who gets excited about fast, full-featured applications and staying up-to-date on the latest tech trends. In his spare time he enjoys producing music and being outdoors.',
37 | linkedin: 'https://www.linkedin.com/in/camsimmons/',
38 | github: 'https://github.com/cssim22',
39 | },
40 | {
41 | firstName: 'Patrick',
42 | lastName: 'Sullivan',
43 | image:
44 | 'https://res.cloudinary.com/os-labs/image/upload/v1625079443/PJS-Passport_hv6kvx.jpg',
45 | info: 'Patrick is a full-stack software engineer passionate about back-end tech. He loves backpacking in the Pacific Northwest and the Rockies, growing vegetables, and living fully.',
46 | linkedin: 'https://www.linkedin.com/in/patrick-j-m-sullivan/',
47 | github: 'https://github.com/pjmsullivan',
48 | },
49 | {
50 | firstName: 'Nhan',
51 | lastName: 'Ly',
52 | image:
53 | 'https://res.cloudinary.com/dyigtncwy/image/upload/v1618454676/Nhan_hlhra2.jpg',
54 | info: 'Nhan is a software engineer that enjoys working on the entire stack and particularly enjoys playing with algorithms and learning about new technologies. When not coding Nhan usually listening to podcasts, reading, and taking (too long) breaks at the gym between sets.',
55 | linkedin: 'https://www.linkedin.com/in/nhanly/',
56 | github: 'https://github.com/NhanTLy',
57 | },
58 | {
59 | firstName: 'Damon',
60 | lastName: 'Alfaro',
61 | image:
62 | 'https://res.cloudinary.com/dyigtncwy/image/upload/v1618454676/Damon_j1ehdh.jpg',
63 | info: 'As a software engineer Damon enjoys databases and experimenting with emerging back-end technologies. He spends his free time backpacking, running, and traveling in his converted school bus RV.',
64 | linkedin: 'https://www.linkedin.com/in/damon-alfaro-28530a74/',
65 | github: 'https://github.com/djdalfaro',
66 | },
67 | {
68 | firstName: 'Adam',
69 | lastName: 'Wilson',
70 | image:
71 | 'https://res.cloudinary.com/dyigtncwy/image/upload/v1618454676/Adam_ddx07d.jpg',
72 | info: 'Adam is a full-stack software engineer originally from Cleveland, OH. He enjoys developing efficient and responsive web applications in addition to performant APIs, using React and Node.js. His favorite things include tacos and Irish wolfhounds.',
73 | linkedin: 'https://www.linkedin.com/in/adam-wilson-houston/',
74 | github: 'https://github.com/aswilson87',
75 | },
76 | {
77 | firstName: 'Christy',
78 | lastName: 'Gomez',
79 | image:
80 | 'https://res.cloudinary.com/dyigtncwy/image/upload/v1618456773/IMG_1522_h9j9vi.jpg',
81 | info: 'Christy is a software engineer focused on developing full stack applications with responsive design. She enjoys salsa dancing, trying new restaurants, unplanned traveling, and being eco-friendly.',
82 | linkedin: 'https://www.linkedin.com/in/christy-gomez/',
83 | github: 'https://github.com/christygomez',
84 | },
85 | {
86 | firstName: 'Geovanni',
87 | lastName: 'Alarcon',
88 | image:
89 | 'https://res.cloudinary.com/dyigtncwy/image/upload/v1618459654/geo2_jxodpa.jpg',
90 | info: 'Geo is a software engineer who loves building scalable and responsive software tools for the developer community. For fun, he enjoys traveling, and picnics.',
91 | linkedin: 'https://www.linkedin.com/in/geo-alarcon/',
92 | github: 'https://github.com/gealarcon',
93 | },
94 | {
95 | firstName: 'Esma',
96 | lastName: 'Sahraoui',
97 | image:
98 | 'https://res.cloudinary.com/dsmiftdyz/image/upload/v1609992320/IMG_2063_htwead.jpg',
99 | info: 'Esma is a full-stack software engineer with a passion for building servers as well as building responsive user interfaces for web applications. In her free time, she is driven by traveling to discover new cultures and kayaking across rivers.',
100 | linkedin: 'https://www.linkedin.com/in/esma-sahraoui/',
101 | github: 'https://github.com/EsmaShr',
102 | },
103 | {
104 | firstName: 'Derek',
105 | lastName: 'Miller',
106 | image:
107 | 'https://res.cloudinary.com/dsmiftdyz/image/upload/v1609990785/Derek-headshot_ofzix3.jpg',
108 | info: 'Derek is a full-stack software engineer with a focus on the MERN tech stack. Outside of coding he loves boardgames and rock climbing.',
109 | linkedin: 'https://www.linkedin.com/in/dsymiller',
110 | github: 'https://github.com/dsymiller',
111 | },
112 | {
113 | firstName: 'Eric',
114 | lastName: 'Marcatoma',
115 | image:
116 | 'https://res.cloudinary.com/dsmiftdyz/image/upload/v1609989762/AE476873-B676-4D4D-AF9A-548B386F7AD7_1_201_a_mxvsgu.jpg',
117 | info: 'Eric is a software engineer from NYC who focuses on front-end development. During his spare time he loves to go to the gym, play basketball and trying new restaurants in his city.',
118 | linkedin: 'https://www.linkedin.com/in/ericmarc159',
119 | github: 'https://github.com/ericmarc159',
120 | },
121 | {
122 | firstName: 'Lourent',
123 | lastName: 'Flores',
124 | image:
125 | 'https://res.cloudinary.com/dsmiftdyz/image/upload/v1609990832/headshot_e4ijvy.png',
126 | info: 'Lourent is a full-stack software engineer specializing in React and Node.js, with a passion for learning new technologies and optimizing frontend web design.',
127 | linkedin: 'https://www.linkedin.com/in/lourent-flores/',
128 | github: 'https://github.com/lourentflores',
129 | },
130 | {
131 | firstName: 'Spencer',
132 | lastName: 'Stockton',
133 | image:
134 | 'https://res.cloudinary.com/dsmiftdyz/image/upload/v1609994346/obsidianpic_gzxcqe.jpg',
135 | info: 'Spencer is a software engineer that enjoys working on solving complex problems across the entire tech stack. When he is not programming, you can find him running on the East River or attending a concert around NYC.',
136 | linkedin: 'https://www.linkedin.com/in/spencer-stockton-643823a4/',
137 | github: 'https://github.com/tonstock',
138 | },
139 | {
140 | firstName: 'Alonso',
141 | lastName: 'Garza',
142 | image:
143 | 'https://res.cloudinary.com/dkxftbzuu/image/upload/v1600479739/Obsidian/WhatsApp_Image_2020-09-18_at_8.38.09_PM_pgshgj.jpg',
144 |
145 | info: 'Alonso Garza is a full-stack software engineer from Austin, Texas. Alonso specializes in React, Node.js, Express, and is passionate about solving complex problems and working in teams. Outside of coding, he loves to travel around Latin America and scuba dive!',
146 | linkedin: 'https://www.linkedin.com/in/e-alonso-garza/',
147 | github: 'https://github.com/Alonsog66',
148 | },
149 | {
150 | firstName: 'Travis',
151 | lastName: 'Frank',
152 | image:
153 | 'https://res.cloudinary.com/dkxftbzuu/image/upload/v1600462593/Obsidian/image_1_c7sggv.jpg',
154 | info: 'Travis is a software engineer at Place Exchange focused on building scalable backend solutions. When he isn’t coding he enjoys playing board games, eating copious amounts of sushi, and conducting pit orchestras.',
155 | linkedin: 'https://linkedin.com/in/travis-m-frank',
156 | github: 'https://github.com/TravisFrankMTG',
157 | },
158 | {
159 | firstName: 'Matt',
160 | lastName: 'Meigs',
161 | image:
162 | 'https://res.cloudinary.com/dkxftbzuu/image/upload/v1600811069/Obsidian/IMG_2543_vrhknw.jpg',
163 | info: 'Matt is a software engineer interested in clear and responsive frontend web design and algorithmic optimization through the full stack. He’s an expat from the Deep South, a lover of far-flung travel, and a former Broadway actor.',
164 | linkedin: 'https://www.linkedin.com/in/matt-meigs/',
165 | github: 'https://github.com/mmeigs',
166 | },
167 | {
168 | firstName: 'Burak',
169 | lastName: 'Caliskan',
170 | image:
171 | 'https://res.cloudinary.com/dkxftbzuu/image/upload/v1600462610/Obsidian/92C676E9-C9C4-4CFB-80A9-876B30C94732_copy_vfsnjl.jpg',
172 | info: 'Burak is a software engineer focused on developing full stack applications. Curious and constantly finding ways to use new ideas to solve problems and provide delight. For fun, he enjoys outdoor activities, traveling, and exploring new cuisines.',
173 | linkedin: 'https://www.linkedin.com/in/burakcaliskan/',
174 | github: 'https://github.com/CaliskanBurak',
175 | },
176 | ];
177 |
178 | const teamMembers = users.map((user, i) => (
179 |
180 | ));
181 | function Team() {
182 | return (
183 |
184 |