First, we will install two Meldio dependencies: Node.js and MongoDB. Once Node.js and MongoDB are in place, Meldio setup is super simple.
Our goal is to create a simple GraphQL backend for the Rockets app.
Our backend will incorporate two types - Rocket
and Launch
and
two mutations - addRocket
and launchRocket
.
The recommended way to install Node.js is with NVM. Please consult nvm installation instructions and then install NVM with the following command:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
Once NVM is installed, open a new terminal window and install Node.js with the following:
nvm install node && nvm alias default node
This will install the latest version of Node.js and alias it as node
. Open a new terminal window and confirm Node.js is set up correctly with node --version
command. This should print the latest version of Node.js.
The recommended way to install MongoDB on an OS X system is with brew package manager, like this:
brew update && brew install mongodb
It is easiest to configure the system to launch MongoDB on startup using the following commands:
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
Alternatively, use the following command to start the Mongo daemon as needed:
mongod --config /usr/local/etc/mongod.conf
For MongoDB installation on various Linux distributions, please consult the MongoDB documentation.
To install Meldio, run the following:
npm install -g meldio
If the system permissions require sudo for installing global npm modules,
the command above will not work. Instead, run npm
with sudo like this: sudo npm install -g meldio
We will now create a new Meldio application. Run the following command
to initialize a new application in the rockets
directory:
meldio init rockets
Meldio will prompt for the following settings:
At this point, Meldio has created and initialized the application directory.
The application is now initialized, so let's enter the rockets
directory and list the files:
cd rockets && ls -la
You should see a file listing that includes the following files and directories:
.env
- file with OAuth secrets and JWT signature key. This file will be ignored by git.eslintrc
- default configuration for linting JavaScript and JSON files.gitignore
- default git ignore configuration. Ignores .env file and .build directoryhooks
- directory with authentication hooks, stub implementations are provided by defaultmutations
- directory with mutation implementations, empty by defaultpackage.json
- standard package file. Meldio configuration is namespaced under config / meldio keyspermissions.js
- functional permissions implementationschema.sdl
- schema definitionLet's start Meldio in the watch mode. Meldio will watch the file system, validate schema, lint the files, transpile them and validate that hooks and permissions are okay on the fly as we are changing the files. Start Meldio in watch mode with:
meldio watch
Meldio will start with this:
building...
analyzing schema...
successfully analyzed 2 schema elements.
linting...
successfully linted 5 files.
transpiling...
successfully transpiled 5 files.
checking hooks...
done with hooks checkup.
checking permissions...
done with permissions checkup.
starting...
__ __ ______ _ _____ _____ ____
| \/ | ____| | | __ \_ _/ __ \ app rockets ver 0.0.1
| \ / | |__ | | | | | || || | | | http://localhost:9000/graphql
| |\/| | __| | | | | | || || | | | auth: ✘ facebook ✘ google
| | | | |____| |____| |__| || || |__| | ✘ github ✘ password
|_| |_|______|______|_____/_____\____/0.4.5
watching...
Open up schema.sdl
, delete everything and add the schema definition for our Rockets app:
type Rocket implements Node @rootConnection(field: "allRockets") {
id: ID!
manufacturer: String!
model: String!
launches: NodeConnection(Launch, rocket)
}
type Launch implements Node {
id: ID!
payload: String!
weight: Float!
timestamp: String
outcome: LaunchOutcome
rocket: NodeConnection(Rocket, launches)
}
enum LaunchOutcome { SUCCESS, FAILURE }
Save the new schema definition and observe that Meldio immediately
warns that we are missing permissions for Rocket and Launch types.
However, since we are not using authentication, delete the permissions.js
file and hooks
directory with the following:
rm -rf hooks permissions.js
Rocket
and Launch
types implement
Node interface. This makes rocket and launch objects directly
addressable from the app using their
globally unique identifiers. In a way, types that implement Node are
analogous to tables in SQL or documents in NoSQL databases.
Note that launches
and rocket
fields within
Rocket
and Launch
types are defined as NodeConnection
.
NodeConnection
is used to create a Relay-compliant cursor connection
between two Node types. In this example, NodeConnection takes two parameters: 1) target type name and 2) field name in the target type that points
back to the originating node (i.e. back-reference). NodeConnection
can also specify properties on the edge between two nodes using type name as the third parameter.
Note that the @rootConnection
directive on the Rocket
type
creates the allRockets
root field which allows us to list all rockets.
Meldio mutations are executed on the server and may incorporate multiple operations that update the state of the backend. In some ways, mutations are similar to stored procedures in SQL databases. The key advantage of using mutations is that they move the code from client applications to the backend, promoting code reuse for the multi-platform apps and improving backward compatibility for the rapidly evolving apps.
Our Rockets app backend will support two mutations:
addRocket
- adds a new rocketlaunchRocket
- adds a new launch for the given rocketFirst, we will add the following mutation signatures to the schema definition file, right after the type definitions:
mutation addRocket(manufacturer: String!, model: String!) {
rocket: Rocket
}
mutation launchRocket(
rocketId: ID!
payload: String!
weight: Float!
outcome: LaunchOutcome
) {
launchEdge: Edge(Launch)
rocket: Rocket
}
Here, the addRocket
mutation simply takes rocket manufacturer and model as inputs,
adds the Rocket
object and returns that object back to the client app.
The second mutation, launchRocket
, takes an id of the rocket that is about to
launch, creates a new Launch
object with the provided payload,
weight and outcome, and creates a connection edge between the rocket and
the newly created Launch
object. The mutation returns that edge along with the updated
Rocket
object to allow the client app to query for aggregates (e.g. total launches) on the rocket object.
As soon as we save the schema, Meldio will report an error indicating that two mutations are defined in the schema but not implemented. Let's fix that now.
Add two files under the mutations
directory:
addRocket.js
launchRocket.js
Let's now implement addRocket.js
:
// addRocket.js
export async function addRocket({ manufacturer, model }) {
// get the Rocket type out of the model
const { Rocket } = this.model;
// add a new Rocket node, with manufacturer and model fields set
const rocket = await Rocket.addNode({ manufacturer, model });
// return the newly added rocket
return { rocket };
}
Then, let's implement launchRocket.js
:
// launchRocket.js
export async function launchRocket({rocketId, payload, weight, outcome}) {
// get the Rocket and Launch types out of the model
const { Rocket, Launch } = this.model;
// get the current timestamp and convert it to String
const timestamp = new Date(this.timestamp).toString();
// if a rocket with the given id does not exist, return nulls
if (!await Rocket.node(rocketId).exists()) {
return { launchEdge: null, rocket: null };
}
// identify the rocket using id
const rocket = Rocket.node(rocketId);
// create a new launch object
const launch = await Launch.addNode({ payload, weight, timestamp, outcome });
// add a connection edge from the rocket to the newly created launch object
const launchEdge = await rocket.launches.addEdge(launch);
// return the launch edge and the rocket
return { launchEdge, rocket };
}
We are all done building, so now let's play with some queries.
With Meldio running, open the endpoint URL, http://localhost:9000/graphql. When a Meldio endpoint is opened in the browser, it will display the query editor we can use to build and validate our queries. Let's add a rocket by copying and pasting the following mutation into the left-hand pane:
mutation AddRocket {
addRocket(input: {manufacturer: "Space X", model: "Falcon 9", clientMutationId: "1"}) {
rocket {
id
manufacturer
model
}
clientMutationId
}
}
Hit Control-Enter and Meldio will return the result similar to the following JSON object (id will be different):
{
"data": {
"addRocket": {
"rocket": {
"id": "-KDthG2xkQJdGktzG80T-hF3B5K",
"manufacturer": "Space X",
"model": "Falcon 9"
},
"clientMutationId": "1"
}
}
}
Now, let's launch that Falcon 9 we just added. Ready? Just replace the rocketId below with the id you received when you executed the addRocket
mutation:
mutation LaunchRocket {
launchRocket(input: {rocketId: "-KDthG2xkQJdGktzG80T-hF3B5K", payload: "SES-9", weight: 11621.0, outcome: SUCCESS, clientMutationId: "2"}) {
launchEdge {
node {
id
payload
weight
timestamp
outcome
}
}
rocket {
launches {
totalLaunches: count
successfulLaunches: count(filterBy: {node: {outcome: {eq: SUCCESS}}})
failedLaunches: count(filterBy: {node: {outcome: {eq: FAILURE}}})
}
}
}
}
Since the launchRocket
mutation returns an updated rocket object,
we can query for aggregates on that object (e.g. total, successful and failed launches)
to refresh that data on the client following execution of the mutation. All this happens
in one round-trip to the backend.
Add a few more rockets and launches and then execute the following query to fetch all that data, all in one round-trip to the backend:
query GimmeAll {
allRockets {
numberOfRockets: count
numberOfSpaceXRockets: count(filterBy: {node: {manufacturer: {eq: "Space X"}}})
edges {
node {
id
manufacturer
model
launches {
totalLaunches: count
successfulLaunches: count(filterBy: {node: {outcome: {eq: SUCCESS}}})
failedLaunches: count(filterBy: {node: {outcome: {eq: FAILURE}}})
totalWeightLifted: sum(node: weight)
maxWeightLifted: max(node: weight)
edges {
node {
id
payload
weight
timestamp
outcome
}
}
}
}
}
}
}
Now, let's move on to some cool example apps.