Skip to content

Instantly share code, notes, and snippets.

@craigspaeth
Created September 19, 2013 22:52
Show Gist options
  • Save craigspaeth/6630959 to your computer and use it in GitHub Desktop.
Save craigspaeth/6630959 to your computer and use it in GitHub Desktop.
A sample of a larger coming blog post about how Artsy shared code client and server for their mobile web app.

A simpler way of sharing code between server & client

Stop abstracting away the server/client environment

Frameworks like Rendr, Derby, and Meteor try to create this magical environment where you can stop thinking about whether your code is running on the client or server. This seems to lead to learning a bunch of concepts unique to the framework that define this "shared" environment. Sometimes that can feel like a leaky abstraction (what is a server-side Backbone view?), or lock you in to the full stack to make it work (Derby/Meteor backed by a REST API instead of mongo?).

Instead of tackling the massive problem of trying to abstract away the server and client, lets acknowledge they're completely different and embrace using modules of code that we can reasonably expect to work on both sides.

Some things that can run on both sides

  • Plain ol' Javascript Logic
    • Domain logic
    • Certain Libraries (e.g. moment, accounting, underscore)
  • HTTP(AJAX) requests
  • Javascript Templates
    • Given you only use & pass in things that can be run on both sides

How do we actually share the code

We use Backbone a lot, so that's an easy choice for wrapping domain logic and HTTP requests into models and collections. Node uses common.js modules so it would be simplest to write code in this manner for the client too. Browserify pacakges common.js, and with plugins like Jadeify we can easily package javascript templates to the client as well.

The tools:

  • Browserify
  • Jadeify
  • Jade
  • Backbone
  • Backbone server-sync

That's it! With these tools you can easily share a good portion of your code on the client and server. We're purposefully avoiding some of the messier glue code like routing/controllers/views that vary quite a bit depending on which environment you're in.

Example app

app.coffee
/models
/collections
/client
/lib
  /client
  /server
  /shared
/templates
  /client
  /server
  /shared

app.coffee

app = require 'express'
Artwork = require './models/artwork'
Backbone = require 'backbone'
backboneServerSync = require 'backbone-server-sync'

app.set 'views', __dirname + '/templates/server'
app.set 'view engine', 'jade'

# Middleware that compiles assets on refresh & override Backbone.sync
app.use require 'compile-assets-with-browserify' if process.env is 'development'
Backbone.sync = backboneServerSync

# Render artwork page
app.get '/artwork/:id', (req, res) ->
  Artwork = new Artwork id: req.params.id
  Artwork.fetch success: (artwork) ->
    res.render 'artwork_page', artwork: artwork

app.listen 3000

/templates/shared/artwork.jade

img( src=artwork.imageUrl() )
h1= artwork.get('artist').name
h2= artwork.get('title')

/templates/server/artwork_page.jade

doctype 5
html
  body
    #content
      include ../shared/artwork
    script.
      BOOTSTRAP = {};
      BOOTSTRAP.ARTWORK = #{artwork.toJSON()};

/models/artwork.coffee

Backbone = require 'backbone'

module.exports = class Artwork extends Backbone.Model
  
  urlRoot: "http://artsy.net/api/v1/artwork"

/client/artwork/view.coffee

require 'zepto'
Backbone = require 'backbone'
Artwork = require '../../models/artwork.coffee'
artworkTemplate = require('../../templates/shared/artwork.jade') # This works b/c of Jadeify

class ArtworkView extends Backbone.View
  
  initialize: ->
    @model.on 'change', @render
  
  render: =>
    @$el.html artworkTemplate(artwork: @model)
  
$ ->
  new ArtworkView model: new Artwork(BOOTSTRAP.ARTWORK), el: $('content')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment