Meteor JS and Apollo: How to write to your MySQL Database
In our last Meteor blog post, we built the standard Hello World app. Behind that simple demo, a whole new world opened for us.
Our Meteor app retrieved the "hello world" greeting from a MySQL database — a feat made possible via Apollo. Our Meteor apps are shackled to MongoDB no longer. We now have an alternative data stack called Apollo that enables a unified and principled approach to retrieve data from any backend service.
Most apps would be pretty useless if all they could do was fetch data though, right? We also need a way to trigger server-side modifications. Fortunately, all this is possible with GraphQL mutations. So, let's carry on by going in the reverse direction. That's right, we're going to write to our MySQL database.
Continue from Where We Left Off
Rather than starting all over, let's build on top of what we accomplished in the last post. If you recall, we:
- Created a Meteor project with the necessary Apollo packages, including React and Meteor integrations
- Implemented the Apollo Server on Meteor's server side, and defined the schema
- Implemented the database connectors and resolve functions
- Tested our server and crafted our query with GraphiQL, an in-browser GraphQL IDE that comes embedded with Apollo Server
- Created the necessary presentation React components to display a list of posts
- Created a data-aware container component that defined and invoked the GraphQL query to fetch the results
This blog post will pick up where we left off. I encourage you to clone or fork the previous post's repository so you can use the same starting point. Alternatively, if you’d like to skip right to the end, you can get the completed repository too.
Follow This Simple Game Plan
Our goal today is to let the user submit a post via the browser that will be saved in our MySQL database with Apollo. I’ll also describe how to add support for GraphQL mutations in your Apollo Server. Similar to how Meteor's Collection API supports inserting or updating new records, a GraphQL mutation is a request that changes the dataset behind the schema.
Next, we’ll implement the submission of GraphQL mutations from your Meteor client. Finally, we'll apply Optimistic UI to get the same, snappy response that Meteor's apps typically deliver through latency compensation.
Make Sure You Are Up-to-date
There is constant progress in the Apollo world. To get the latest packages, let's first run:
> meteor update
> meteor npm update
Since some of these packages are still pre-1.0, it's important to note that their APIs will most likely change. Here are the versions of some key packages we’re using at this time:
METEOR@1.4.1.2
apollo@0.1.2
apollo-client@0.4.21
apollo-server@0.2.8
graphql@0.7.2
graphql-tag@0.1.14
graphql-tools@0.6.6
graphql-typings@0.0.1-beta-2
Update Your Apollo Server
So if you’re all caught up, your Apollo Server should be able to query for posts, but not add new ones. So, let's add this to your API.
Add the Mutation to Your Schema
Changing the structure of the data or the ways to fetch and mutate it means changing the GraphQL schema. GraphQL is unapologetically client-centric, so starting with your schema allows you to think client-first, then worry about the complexities of supporting that schema later.
Here, we take our existing schema and define a mutation that allows clients to add a post:
We start by defining a new type, Mutation, with a field, addPost. It takes two arguments, content and views. That '!' at the end of the content String type means it's required. Meanwhile, views is optional. What does addPost return? While it can theoretically be anything we want, we will return the new Post since that's likely what a client might need. With this new type defined, we then add it to the schema.
Project Ricochet is a full-service digital agency specializing in Meteor, Apollo, and GraphQL.
Is there something we can help you or your team out with?
Add the Resolver
Similar to the workflow we followed in our last blog post, we can now implement the resolve functions that support our schema. Here, we need a single function that handles the newly added addPost mutation.
All the hard work of mapping our Javascript object to a MySQL table was already done in our previous blog post. If you recall, we implemented this data connector:
We don't need to make any further changes. The object-relational mapping (ORM) package that we used, Sequelize, has everything it needs to query the Post table and insert it as well via its create function. This function also returns the newly created post object — just like our schema declares addPost should. Thus, we simply need to call Sequelize's Post.create(args) in our resolve function to add the object to MySQL and we're done!
Test in GraphiQL
Now that that's all set up, we can once again test our Apollo Server and our newly added mutation with GraphiQL. Apollo Server provides this as an easy way to explore your schema, test your GraphQL server, and craft your queries and mutations. Start your meteor app and go to:
http://localhost:3000/graphiql
Next, enter the following GraphQL query:
mutation { addPost(content: "writing to the database!") { id, content, views } }
If everything is set up correctly, you'll see the newly added post in the response.
You can confirm it was inserted into your MySQL database, too.
mysql> select * from posts order by id desc limit 1;
In our app, we're going to allow our user to enter any text for the post. This means we can't hardcode it like we've done above. GraphQL supports this through query variables.
Change the mutation to the following:
mutation ($content: String!, $views:Int) { addPost(content: $content, views:$views) { id, content, Views } }
This mutation expects a $content and optional $views variables, which you can enter in the Query Variables section of GraphQL.
{"content": "writing to the database via a query variable in GraphiQL"}
Run that mutation, and you should see this:
Congratulations, you've successfully added a mutation to your Apollo Server. Now let's move over to the client side to take advantage of this new capability.
Update Your Apollo Client
Users will need a way to input a message to post. At minimum, this means you need a form with an input text field. Let's add a couple of packages to help us with the layout. I'll use Materialize here, but feel free to use your favorite front-end HTML/CSS framework. This doesn't impact our use of Apollo or the ability to send GraphQL requests in anyway.
> meteor add fourseven:scss
> meteor add materialize:materialize
Create Your Form
In our original blog post, we created a presentation component, Posts, that rendered posts passed in via its props. We then wrapped it using react-apollo's graphql function to create a data-aware container, PostsContainer. It had the responsibility of calling the query and passing the results to Posts.
We can follow this same pattern with mutations too. First let's define the presentation component:
Here is a straightforward React component, MessageForm, that renders a form with a single input text field. You might notice there aren't any references to Apollo or GraphQL. The primary responsibilities of this component are to render the UI and capture the form events. That's it! It delegates the work of submitting this post to the server, to the function in its submit prop. This function is called in submitMessage, which is invoked when the form is submitted.
Create Your Container Component
So, where does this function come from? It's provided by this component's container:
MessageFormContainer handles the integration with Apollo and provides a way for MessageForm to call the GraphQL mutation. Here, we add the very same mutation we crafted in GraphiQL and assign it to the addPost constant.
We call the same graphql function that we used to create PostsContainer, but this time we wrap the MessageForm component to create the MessageFormContainer.
The first argument is the query — in this case, our mutation, addPost. Since it’s a mutation, Apollo provides a function to call this mutation, in the form of ApolloClient.mutate. It passes it to the wrapped component, MessageForm, via a mutate prop by default. This function would allow MessageForm to invoke the mutation by calling:
this.props.mutate({ variables: { content: inputFieldValue } })
However, we implemented MessageForm to expect a simpler function in its submit prop, to be used like:
This simple function hides the complexities of Apollo Client's mutate function. It may not seem that much more difficult to use, but we'll be adding more complexity to our ApolloClient.mutate function call later on.
To make this possible, we’ll provide a second parameter to the graphql function. This allows for further customization to the container component. The parameter has a props property that we can assign to a function. It will take in the original props and return an updated version that will be passed to the wrapped component. We use this to replace the mutate function prop with a simpler submit function prop.
Next, we extract the ApolloClient.mutate function from the original props via destructuring. Then, we return an object literal as the new props, with a function, assigned to submit, that takes in the message argument and invokes ApolloClient.mutate with it. To put it simply, we're wrapping ApolloClient.mutate with this easier-to-use submit function.
This is the suggested pattern provided in the React Apollo Guide. I admit that it can be a bit of a mindbender. There’s a lot going on in just a few lines — so it may take a few passes to understand it. But once you do, I believe you’ll see that it’s an elegant way to abstract Apollo and its complexities from the presentation component. Why would we want to do that? Well, besides simplifying our presentation component, it allows us switch out Apollo with another data framework or a testing stub without changing MessageForm.
Add the Container Component to Your Layout
Now it's time to add the MessageFormContainer to your layout.
We're using Materialize's grid to lay out both our previously created PostsContainer and our new MessageFormContainer together.
If everything went well, you should now be able to enter some text into the input field, and hit enter. After a few seconds, you should see the new post (your message) appear in the list of posts.
So, you’ve proven that you can read from the database. Now, you can write to it as well!
Think Optimistically
Still, something's not right. If you've added a handful of posts, you'll notice that the post can sometimes take a few seconds to appear. This is because the app is polling for data changes. Even though your mutation successfully inserted a new post, it won't display again until the database is polled. These days, users expect instant feedback. With Meteor, we could help expedite performance of the app by taking advantage of its latency-compensation. That would allow us to optimistically update the client before a round-trip to the server was complete.
Apollo's Optimistic UI achieves the same effect. However, it's slightly more work to activate it.
We first need to update our ApolloClient configuration:
Apollo Client comes with a special object store that caches the objects returned in GraphQL responses in a normalized structure. It also associates them with their queries. Thus, when an object is updated, the associated queries can be notified. However, we need to ensure that a unique ID for each object can be generated. This helps Apollo's store de-duplicate the same object from multiple queries, and map queries to the objects. So, let's make the required changes to generate a unique object ID.
Can Meteor scale?
Learn more about Meteor performance with our white paper on the "Top 10 Meteor Performance Problems" and how to address them.
To do this, we specify the queryTransformer: addTypeName when we instantiate ApolloClient. This automatically adds the __typename meta field to types in our GraphQL responses, with the value being the name of the object type, like "Post." This will be enabled by default in apollo-client@0.5, but is needed at the time of this writing with apollo-client@0.4.21.
Next, we can define a custom dataIdFromObject function, which generates a unique ID for every object in a response. As you can tell from our function, it uses a combination of the __typename and the object ID to create a unique identifier for that object.
When a mutation updates an existing object, Apollo’s store will know which queries involved with this object will need to be notified of this change. However, when a modification results in a new object, there is no existing object in the store to update. So, when removing or inserting a new object like we're doing here, we need to explicitly tell Apollo how to handle this change. We also need to tell Apollo what a GraphQL response would look like if the mutation was successful. We do this via the updateQueries and optimisticResponse options on the ApolloClient.mutate function.
Let's also add the
> meteor npm install react-addons-update --save
Next, we define the updateQueries function. This function will be given the current result of the PostsForDisplay query via the prev argument. If you recall, we defined this query in postsContainer.js. It then pushes the new post, retrieved from mutationResult.data.addPost, into the query’s current result, creating a new version to be added back into the Apollo store. Apollo will then reactively update the UI wherever the PostsForDisplay query was used.
Now, let’s utilize the optimisticResponse option. We can set it to what the mutation response would look like if the insert were successful. This will allow the client to use this as a temporary response until the real one comes in from the server. If the mutation was unsuccessful or had a different response, the Apollo Client will replace this faked object with the actual one.
Notice how we’re setting the id to some random number? Meteor’s existing latency compensation benefits from a shared seed that would generate the same ID in MongoDB on the server, and Minimongo on the client, for the newly created object. Since we won’t always be able to control how our backend services generate their IDs, that’s not possible with Apollo’s Optimistic UI. The simple approach used here is to give it a random ID for now. It will be replaced with the real value when the round trip with the server completes. In most cases, it’s all transparent to the user. However, it’s not ideal. If the generated ID already exists, the object that shares this ID will be replaced in the store until it is corrected when the actual mutation’s response arrives. Perhaps in the future the Apollo store can infer the next likely ID, or generate a special temporary one that is guaranteed to not clash with an existing object.
For now, with these two options configured, you have Optimistic UI enabled for this mutation. Try it out and you should see an almost immediate update in the display of posts when a new one is added.
Wrap Things Up
Yes, these were basic examples. And, there's still much to learn in this area — like real-time updates via subscriptions and integrating Meteor accounts. We'll aim to cover these in the near future. But these simple exercises should enable you to experiment with the basics of reading and writing to backend services using Apollo.
More importantly, I hope you are getting a better sense of how using Apollo with Meteor compares with Meteor's existing data framework. Do you need the power and flexibility of a data stack like Apollo? Or, the simplicity of Meteor's Collections and Methods? While it does seem to have a higher learning curve, it's hard to ignore the tremendous momentum behind GraphQL happening in the Javascript community. Not to mention, the world of possibilities it opens for developers.
Curious about how much it might cost to get help from an agency that specializes in Meteor & Apollo?
We're happy to provide a free estimate!