Building a Cardstack app - Part 1
24 November 2017
What is Cardstack?
Cardstack is an open-source application framework, built on top of Ember.js and Node, that aims to make common application features (CMS, notifications, authentication) a snap to add to your application and empowering the end-user to do so.
It is also special in that it also provides a server-side component as part of the framework, so the front- and back-end can really work in tandem.
The Cardstack team has a grand and worthy vision for the future of software and I strongly encourage you to read the whitepaper (and I dare you not to get enthusiastic about the project once you've done so).
In this series of blog posts, I'm going to show you how to get started with Cardstack by leading you through the building of a simple app.
The tutorial app
We're going to integrate Cardstack in the "Super Rentals" app developed in the official Ember.js tutorial. It's a really small app but it's big enough to highlight some of the features Cardstack gives you.
Unsurprisingly, it's built around rentals, and its main page shows a list of rentals that can be filtered by city.
Each rental can then be examined on its own page. There is also a simple About us and Contact page.
First part: Welcome to Cardstack
In this first installment of the tutorial, we'll see how to integrate Cardstack into this app until the point where it displays the main page with a welcome message.
We're going to do a lot of groundwork, things that don't have an immediate, user-facing result, but which will allow us to build really fast and high later.
Let's jump in.
Setting up the app
If you'd like to follow along, which I encourage you to do, you first have to check out the super-rentals repo:
1$ git clone https://github.com/ember-learn/super-rentals.git
We'll use yarn as our package manager, so you'll have to install it if you haven't already done so. To be able to run the app, you'll want to have a recent version of node, 8 is best.
Change to the folder you have cloned the repo to and install the project's dependencies:
1$ yarn
When you have this, start the app by launching ember serve
. When you go to http://localhost:4200
, you should see the list of rentals:
Setting up CardStack dependencies
Cardstack is a search-first web application and is powered by Elasticsearch. To make it easy for developers to set up Elasticsearch (and to prevent a slew of incompatibility and configuration issues), Cardstack also published a Docker repository you can download.
(If you don't yet have Docker on your machine, you'll first have to install it).
You can get the elasticsearch repository by issuing:
1$ docker pull cardstack/elasticsearch:dev
And then running it using the following command:
1$ docker run -d -p 9200:9200 --rm cardstack/elasticsearch:dev
Routing
If we peek into app/router.js
, we'll find a handful of routes:
To have these routes managed by Cardstack, we first need to add the @cardstack/routing
package:
1$ yarn add @cardstack/routing
Let's go back to the router, remove the existing routes and extend it with the Cardstack ones:
1// app/router.js 2import EmberRouter from '@ember/routing/router'; 3import config from './config/environment'; 4import { cardstackRoutes } from '@cardstack/routing'; 5 6const Router = EmberRouter.extend({ 7 location: config.locationType, 8 rootURL: config.rootURL 9}); 10 11Router.map(cardstackRoutes); 12 13export default Router;
cardstackRoutes
will contain the routes auto-generated by the framework for the models we're about to define.
The all-important data schema
When launching the application with ember serve
we get the following error:
Now that we have the hub, the main server-side piece of the hub architecture, we need to define the models there. Cardstack will then create models, routes and server endpoints(!), the full monty, based on the schema defined in the host application.
Let's create the directory structure cardstack/seeds/development
from our project folder:
1$ mkdir -p cardstack/seeds/development
And then create a models.js
file in that folder to define our data schema:
1// cardstack/seeds/development/models.js 2 3const Factory = require('@cardstack/test-support/jsonapi-factory'); 4 5module.exports = [ 6 { 7 type: 'data-sources', 8 id: 'default', 9 attributes: { 10 'source-type': '@cardstack/ephemeral', 11 params: { 12 initialModels: initialModels() 13 } 14 } 15 }, 16 { 17 type: 'plugin-configs', 18 id: '@cardstack/hub', 19 relationships: { 20 'default-data-source': { 21 data: { type: 'data-sources', id: 'default' } 22 } 23 } 24 } 25]; 26 27function initialModels() { 28 let factory = new Factory(); 29 30 factory.addResource('content-types', 'pages') 31 .withAttributes({ 32 'routing-field': 'permalink' 33 }) 34 .withRelated('fields', [ 35 factory.addResource('fields', 'title') 36 .withAttributes({ 37 'field-type': '@cardstack/core-types::string' 38 }), 39 factory.addResource('fields', 'body') 40 .withAttributes({ 41 'field-type': '@cardstack/core-types::string' 42 }), 43 factory.addResource('fields', 'permalink') 44 .withAttributes({ 45 'field-type': '@cardstack/core-types::string' 46 }), 47 ]); 48 49 return factory.getModels(); 50}
Don't worry about the module.exports
part too much, the only thing we care about now is that it calls the initialModels
function for the data source which should return the model definition.
Since we want to build a little CMS and work with pages, the first model we define therein should be page
. Cardstack uses the JSON API format for defining data schema and the jsonapi-factory provides methods, like withAttributes
and withRelated
, to help create the documents that describe that data.
The page
content type (the plural form is used in JSON API) has a routing-field
with a value of permalink
, which is the property used by the routing package to create and traverse URLs.
It also has fields, which is defined as a relation to the built-in fields
content-type. Then come the fields themselves, each having an id (like title
, body
, etc.) and a field-type
.
Good, so we've now defined the page
schema (the Page class, if you will), we can now create page
instances.
For now it suffices to have a main page, so let's create one in the same file:
1// cardstack/seeds/development/models.js 2 3const Factory = require('@cardstack/test-support/jsonapi-factory'); 4 5module.exports = [ 6 (...) 7]; 8 9function initialModels() { 10 (...) 11 12 factory.addResource('pages').withAttributes({ 13 permalink: " ", // a string with a single space 14 title: "Welcome to Cardstack!", 15 }); 16 17 return factory.getModels(); 18}
Before we go on, let's add the @cardstack/test-support
package to our dependencies as we're importing the jsonapi-factory
module from there:
1$ yarn add @cardstack/test-support
The next error we receive is:
1> cardstack.index @cardstack/routing tried to use model page but it does not exist
We defined our data schema but for Cardstack to generate client-side models for it, we also need the @cardstack/models
package, so let's install it:
1$ yarn add @cardstack/models
After a restart, we now get another error, but not to despair, keep in mind that receiving a different error each time is progress:
1Mirage: Your Ember app tried to GET '/cardstack/api/pages?branch=master&filter[permalink][exact]= &page[size]=1',
Oh, ok. The Super Rentals app uses Mirage for mocking server responses, but Cardstack has a built-in server side that implements the CRUD actions based on our model schema, so we don't need Mirage.
We have to replace Mirage with the @cardstack/jsonapi plugin:
With that change, we again got closer to have a working, Cardstack-enabled landing page. The page header is rendered but when it gets to the header link, it bows out:
1You attempted to define a `{{link-to "index"}}` but (...). There is no route named index"
Since we took over routing by way of the @cardstack/routing
package, we have to use routes generated by it and the helpers it provides. In the header, we want to link to the main page that we created in the models seed file and we achieve it by using the cardstack-url
helper (provided by @cardstack/routing
) which takes a type and an id.
Let's open the application template and replace the first link-to
, while temporarily removing the links to the about and contact page:
1<!-- app/templates/application.hbs --> 2<div class="container"> 3 <div class="menu"> 4 <a href={{cardstack-url 'page' ' '}}> 5 <h1> 6 <em>SuperRentals</em> 7 </h1> 8 </a> 9 <div class="links"> 10 <!-- we removed the other links from here --> 11 </div> 12 </div> 13 <div class="body"> 14 {{outlet}} 15 </div> 16</div>
The header is now rendered and the body contains the error message: "No such component cardstack/page-page".
Great, we now we got to the core customization feature of Cardstack, components.
Cardstack components
Cardstack components is the main way of making your app look and behave the way you want it to while still having all the features the framework provides for you.
The naming rule for which component is rendered for a certain type of resources is
1`cardstack/${resource-type}-${format}`
In our case, we have a resource of type page
as the main page of the app. Since this object represents the whole page, Cardstack, by convention, tries to render a "page" object in "page" format, and that's why it complains about not finding a cardstack/page-page
component.
This might be confusing, so let me give you another example. If you have a resource of type rental, and a 'card' format, Cardstack will render the cardstack/rental-card
component.
Rendering the main page
Ok, so we established we'd have to define a cardstack/page-page
component to put some content on the main page. We already have a title property for pages so let's see if we can make it appear.
We'll create a template file for the component manually as we don't need the javascript file, and put the following content into it:
The content
property in Cardstack components refers to the resource (in this case, the page).
The app reloads and our welcome message appears!
What did we accomplish?
We started with the simple Super Rentals app, and we added a few necessary Cardstack packages to enable easily adding features to it, which we're going to do in the next part of the series.
We laid down the groundwork, and now construction can begin.
In the meantime, you can check out the official Cardstack site, follow its development, or ask us any questions you might have in our Slack group.
See you next time!
Share on Twitter