iceflow-server

0.3.8 • Public • Published

#Iceflow Server

Iceflow-server is a react isomorphic server concept written to explore the feasability of matching the Express api on both client and server. It's intended to be both turn-key and opinionated. It is built on Express and uses React with Redux/React-Redux.

##Quick Start

For an example server setup git clone https://github.com/mikehoren/iceflow-server-example

##Installation

npm install iceflow-server --save

##Server Structure

/app
-- /components
-- Application.js
-- /controllers
-- /reducers
-- routes.js
/config
/logs
/public
-- /sass
-- /build
-- /fonts
-- /images
/server
-- /api
-- /actions
-- /routes
-- /lib
-- /migrations
-- /models
-- bootstrap.js
/tests
/utils
/views
app.js

##Building the App

###Setup

Sample app.js

'use strict';

const express      = require('express');
const path         = require('path');
const favicon      = require('serve-favicon');
const logger       = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser   = require('body-parser');
const port         = 3000;
const sessions     = require('client-sessions');
const colors       = require('colors');
const app          = require('iceflow-server');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(sessions({
    cookieName: 'session',
    secret: 'iceflow',
    duration: 24 * 60 * 60 * 1000,
    activeDuration: 1000 * 60 * 5
}));

app
  .setup({
    ignore: [/node_modules\/(?!iceflow)/],
    redirects: {
      loggedOut: 'redirect'
    }
  })
  .addSessionMapper(function(data){
    return {
      user: data.user
    }
  })
  .load()
  .listen(port);

####Application Methods

  • app.setup(options) - Autoloads the built in middleware and the api routes. This method takes an optional options object.
  • app.addSessionMapper(fn) - this method takes a function which is used as a mapping function to determine what session data to pass to the client.
  • app.load() - this method loads your React application on the server.
  • app.listen(port) - Start the server and listen on the designated port.

app.setup() options

  • ignore [RegExp] This regular expression gets passed to the server's babel ignore option
  • redirects [Object] This object has two possible keys, loggedOut and LoggedIn. The String value of these is the query param key appended to your routes redirect url specified in /app/routes.js. The value will be the original url. Useful for pages that require being logged in but once a user logs in you would like to redirect to the original requested page.

###Components

Create an Application.js component in app/components as your root react component. Require react-redux when you want to connect with the store. From within your main Application component the page to view will be available as the prop Page.

###Routes

The routes files contain your navigatable application routes.

'use strict';

const routes = {

  '/': {
    //the controller to use
    controller        : 'Index',
    //the action to call on the controller
    action            : 'index',
    //the component to use as your base component for the page
    component         : 'index/Page',
    //redirectIfLoggedIn/redirectIfLoggedOut if your user has a session the router will redirect the user to the url you designate
    redirectIfLoggedIn: '/dashboard'
  }

}

module.exports = routes;

###Controllers

Controllers run when the user navigates to a url specified in your routes. If the url has no match in routes Iceflow-server will assume you want the NotFound controller with an index action and will attempt to load components/not_found/Page. Controllers run on the client and server and it's expected they will only make GET requests.

Example controller

'use strict';

const Controller = require('iceflow-server/lib/Controller');

class Index extends Controller {

    constructor(){
        super();
    }

    index(req, res){
    this.render(req, res, {
      title     : 'Iceflow Server'
    }); 
    }

}

module.exports = new Index();
  • render(req, res, data) - the render method will trigger the app to render the associated action for the route. The data property will be passed to the final markup on the server. On the client it will be saved to the store using lodash.assign.
this.render(req, res, {
    title     : 'Iceflow Server Dashboard',
    data      : {
        key: 'value'
    }
});

###HttpService

The HttpService module is used to make CRUD requests or parallel GET requests.

  • fetch(spec) - fetch will make parallel requests based on the passed in spec. The spec should take the form {key: {url: '/', data: {}, api: 'someApiKey'}} and return {key: {key: 'value'}}. This method returns a promise.

The api key is intended to be a string which matches up with an api endpoint in your application.js config file. This endpoint will server as the host for your request. The url will be appended on the end. The url property is always intended as relative, either to the server or to the api.

HttpService.fetch is the ideal way to make requests at the controller level.

An example fetch request

const HttpsService = require('iceflow-server/lib/HttpService');

HttpService.fetch({
  users: {
    action: '/users',
    api   : 'test'
  }
}).then( data => {})

The api key is matched to config/Application.js

module.exports = {

  apis: {
    test: 'https://someexternalapi.com'
  }

};

On the server for non-remote routes fetch will attempt to run the actual actions by convention.

{
  action: '/users'
}

would get api/actions/users/get.js

{
  action: '/users/example'
}

would get api/actions/users/get_example.js

{
  action: '/users/1'
}

would get api/actions/users/get_show.js.  Numeric values for a specific resource get converted to getShow.

For params and query data the fetch method has a data key. Example:

const HttpsService = require('iceflow-server/lib/HttpService');

HttpService.fetch({
  users: {
    action: '/users/1',
    data: {
      params: {
        id: 1
      },
      query: {
        x: 2
      }
    }
  }
}).then( data => {})

You can retrieve and map headers from the response using the headers property. Headers are only mapped if they're set to a key which does not exist on the object passed into fetch (using lodash defaults).

const HttpsService = require('iceflow-server/lib/HttpService');

HttpService.fetch({
  users: {
    action: '/users/1',
    headers: function(headers){
      return {
        my_prop: headers.my_prop (1)
      }
    }
  }
}).then( data => {
  console.log(data.my_prop) === 1
})
  • get/post/put/patch/delete (object) - Crud methods take an object in the form {url: '/', data: {}, cors: false} and returns a promise.
const HttpsService = require('iceflow-server/lib/HttpService');

HttpService.post({
    url: '/users',
    data: {
        email: 'some@email.com'
    }
})
.then( response => {})
  • render(req, res, data) - An alias for Renderer.render, call when you're ready to render the page.

###Events

The Events module is the central event aggregator for your app. It's just an alias for node/events. The "on" method of Events is a wrapper for the native method, this method by default will not add events on the server to prevent memory leaks. If you need to add events on the server set a third argument as true for "on". There are a few built in events used for routing. These events are intended for the client only.

route - This will trigger the router to route to the passed in url

const Events = require('iceflow-server/lib/Events');

Events.emit('route', '/newurl');

route:start - this will trigger as soon as the router begins to route to a new url.

const Events = require('iceflow-server/lib/Events');

Events.on('route:start', doSomething);

route:set - This will trigger after the url changes but before the controller action is run.

route:complete - This will trigger after the controller action but before the application is rerendered.

route:mounted - This will trigger when routing is complete.

###Reducers

Reducers for your redux store should be located in app/reducers. The server will look at reducers.js and use the exported object to create the single reducer. An example app/reducers/reducers.js file might look like

const reducer1 = require('./reducer1');
const reducer2 = require('./reducer2');

module.exports = {
  reducer1,
  reducer2
};

###Request and Response Objects

For the most part you can use these normally both on the backend and frontend in the context of /app. There are some built in helpful additions/modifications to the request object from within your controller files.

req.fetch() - An alias for HttpService.fetch().

req.store - Each request has a seperate store on the server, on the client it points to the same single store.

req.route - Contains the route configuration for the requested route from your route config.

###Middleware

By default two middleware files are loaded, multipart and response. Additionally the server will also look in the ./server/middleware folder for any additional middleware you would like to add.

  • Multipart Middleware automatically handles multipart forms for you.
  • Response middleware adds 3 methods, forbidden, badRequest and unauthorized to the response object. Each takes an object that is JSON.stringified and passed as the response with the appropriate response codes.

###Conventions/Hooks

Internally the server will attempt a number of different files at startup.

  • app/routes.js - These contain the routes that are navigatable in your app.
  • config/application.js - These contain application configurations used on both the backend and FRONTEND. In this object it uses the apis key when calling fetch() in HttpService. The pushstate key is used to determine if the app should use pushstate or server-render every page.
  • views/index.ejs - The page layout that is server-rendered.
  • server/bootstrap.js - This is a hook to do server things and is run before the router is initialize (navigatable routes are loaded) but after server api routes and internal middleware is loaded.
  • app/reducers/reducers.js - Redux reducers are pulled in and applied to the app's store at startup. It's expected functions will be exported in each file with the signature (state, action).
  • server/api - Server routes are autoloaded when app.setup() is called. A server route consists of two parts. The routing part is in server/api/routes. The action is in server/api/actions. By convention actions should start with the method of the request.

in server/api/routes/users.js

'use strict';

const getAction         = require('../actions/users/get');
const postExampleAction = require('../actions/users/postExample');

module.exports = app => {
  app.get('/api/users', getAction);
  app.post('/api/users/example', postExampleAction);
}

In terms of how you structure the other server-related pieces of your app including databases and migrations that's complete up to you and you have plenty of flexibility on how to approach these parts.

Readme

Keywords

none

Package Sidebar

Install

npm i iceflow-server

Weekly Downloads

2

Version

0.3.8

License

ISC

Last publish

Collaborators

  • mikehoren