├── .gitmodules ├── LICENSE └── readme.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "examples/auth-example"] 2 | path = examples/auth-example 3 | url = https://github.com/YerkoPalma/auth-example.git 4 | [submodule "examples/simple-example"] 5 | path = examples/simple-example 6 | url = https://github.com/YerkoPalma/simple-example.git 7 | [submodule "examples/choo-example"] 8 | path = examples/choo-example 9 | url = https://github.com/YerkoPalma/choo-example 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yerko Palma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # full-stack 2 | 3 | > full-stack project template 4 | 5 | ## Features 6 | 7 | - Exposes a REST API prefixed with `'api/:version'`, like `api/v1`. 8 | - Use merry to serve the api, and bankai for assets. 9 | - Handle environment variables through .env file (remember not to commit the file). 10 | - Use leveldb as database (memdb in development, leveldown in production). 11 | - Use `npm scripts` as task runner. 12 | 13 | ## Usage 14 | 15 | To use it, copy the [savior][savior] script somewhere in your path, then clone 16 | my [templates][templates], and set the `TEMPLATES` environment variable to the 17 | path where you cloned the templates. After that, run: 18 | 19 | ```bash 20 | $ savior full-stack 21 | ``` 22 | 23 | ## How it works 24 | 25 | There are two main frames in this stack, the client (frontend) and the api 26 | (backend). 27 | 28 | ### Api 29 | 30 | The api files remain in the `/api` folder. The folder structure is the following 31 | 32 | ``` 33 | api/ 34 | ├── lib/ 35 | │ ├── factory.js 36 | │ └── resource.js 37 | ├── models/ 38 | └── index.js 39 | ``` 40 | 41 | Generally, you don't need to modify anything in the `lib` foler, 42 | the files in there will help you to generate your model instance and the REST 43 | routes for that model. We will get to that later. 44 | 45 | The `models` folder contains your models schemas as json files, just that. 46 | Be aware that the name of your model file, must be the same that you use later 47 | to get your model instance. 48 | 49 | The `index.js` file is the entry point of your api. It MUST expose (exports) a 50 | [merry][merry] instance, that will be served by the root index file (the server). 51 | Here is where you use the lib folder files. First create your models, which are 52 | [rest-parser][rest-parser] instances with the `factory.js` file, like this: 53 | 54 | ```js 55 | var Model = require('./lib/factory') 56 | var Post = Model(db, 'post') 57 | ``` 58 | 59 | In the above snippet, `Post` is your model, and `db` is a [levelup][levelup] 60 | instance (in this example [memdb][memdb] for development and [level][level] for 61 | production). After you define that model, define the REST routes for that model 62 | 63 | ```js 64 | var Resource = require('./lib/resource') 65 | var merry = require('merry') 66 | 67 | var app = merry() 68 | var resource = Resource(app) 69 | 70 | resource(Post, opt) 71 | ``` 72 | 73 | As you can see, the `Resource` function expect a merry instance, and returns a 74 | function that will attach REST routes to your app, by providing a model (`Post` 75 | in the above example) and a set of options, which can/must be: 76 | 77 | - version: can be anything, if not defined will default to 1. 78 | - path: MUST be provided, and be exactly the same that the name of your model 79 | scheme file 80 | - only: an array of strings, specifying that you only need support for those 81 | actions, possible actions are: `'create', 'show', 'index', 'update', 'delete'` 82 | 83 | Now you are ready to expose a simple, but fully featured REST api. If you need 84 | specific logic for certain routes, just define them as you would with any other 85 | merry app. 86 | 87 | ### client 88 | 89 | The client source code of the app lives in the `/src` folder, the folder 90 | structure is the following: 91 | 92 | ``` 93 | src/ 94 | ├── assets/ 95 | ├── store/ 96 | │ ├── actions.js 97 | │ └── reducer.js 98 | ├── views/ 99 | └── index.js 100 | ``` 101 | 102 | Most of the client is handled by [singleton-router][singleton-router] and 103 | [redux][redux]. `views/` folder contains views, which are files that expose a 104 | function that gets as argument the `params` and the redux `store`, and, using 105 | [bel][bel], return an HTML element. To comunicate with the backend (api), you 106 | should use the actions from the `store` folder, there is a helper function 107 | `makeRequest` which use the http module from node to make an xhr request 108 | (thanks to browserify), anyway, you can replace it for any other xhr/ajax 109 | library 110 | 111 | ## FAQ 112 | 113 | ### How should I handle authentication stuff? 114 | 115 | You should use middlewares. In the api, you can pass a [nanostack][nanostack] 116 | instance to the resource library in your `/api/index.js` file, like this: 117 | 118 | ```js 119 | var app = merry() 120 | var stack = Nanostack() 121 | // push to middleware 122 | var resource = Resource(app, stack) 123 | ``` 124 | 125 | That's the way to go with middlewares. 126 | 127 | ### What about production? 128 | 129 | For production you can build with `npm run build`, that would use [bankai][bankai] 130 | to build your frontend, then run your app with the `ENV` variable set to 131 | `production` like `ENV=production node index.js`. So what's different? 132 | 133 | - No loggin in production, only for errors. 134 | - Use level in production instead of memdb. 135 | - All your frontend is minified, thanks to bankai. 136 | 137 | ### Do you use components? 138 | 139 | Components doesn't get included here. If you want components, you should use 140 | [microcomponent][microcomponent] library. 141 | 142 | ### And server side rendering? 143 | 144 | Let's say you have a component or something you want to server render, then you 145 | should define it normally in your client and then in your main index file, take 146 | it and respond with it as a stream when there is an html request, for example: 147 | 148 | ```js 149 | var fromString = require('from2-string') 150 | var nav = require('./components/nav') 151 | var hyperstream = require('hyperstream') 152 | var http = require('http') 153 | var path = require('path') 154 | var fs = require('fs') 155 | 156 | var server = http.createServer(handler) 157 | server.listen(port, ip) 158 | 159 | function handler (req, res) { 160 | var url = req.url 161 | if (url === '/') { 162 | serveHtml(res, index) 163 | } 164 | } 165 | 166 | function serveHtml (res, html) { 167 | htmlStream = fs.createReadStream(path.resolve(__dirname, html)) 168 | navStream = hyperstream({ 169 | 'header': fromString(nav.toString()) 170 | }) 171 | htmlStream.pipe(navStream).pipe(res) 172 | } 173 | ``` 174 | 175 | The above snippet take the nav component (a `bel` component) and make a stream 176 | of it with [from-string][from-string]. Then it uses [hyperstream][hyperstream] to 177 | make an html stream and pipe it to the response stream. 178 | 179 | ## Example 180 | 181 | In the example folder, there are some sub projects with their own description. 182 | 183 | ## License 184 | 185 | [MIT](/license) 186 | 187 | [savior]: https://gist.github.com/YerkoPalma/c9814be639efb165e8445667f36b901e 188 | [templates]: https://github.com/YerkoPalma/templates 189 | [rest-parser]: https://github.com/karissa/node-rest-parser 190 | [memdb]: https://github.com/juliangruber/memdb 191 | [levelup]: https://github.com/level/levelup 192 | [level]: https://github.com/level/level 193 | [singleton-router]: https://github.com/YerkoPalma/singleton-router 194 | [redux]: https://github.com/reactjs/redux 195 | [nanostack]: https://github.com/yoshuawuyts/nanostack 196 | [microcomponent]: https://github.com/yoshuawuyts/microcomponent 197 | [from-string]: https://github.com/yoshuawuyts/from2-string 198 | [hyperstream]: https://github.com/substack/hyperstream 199 | [merry]: https://github.com/shipharbor/merry 200 | [bankai]: https://github.com/yoshuawuyts/bankai 201 | [bel]: https://github.com/shama/bel --------------------------------------------------------------------------------