https://github.com/usehenri/henri
The versatile Javascript framework
https://github.com/usehenri/henri
disk framework graphql hacktoberfest henri mongoose nextjs nodejs react react-server-render server-side-rendering vue
Last synced: 5 months ago
JSON representation
The versatile Javascript framework
- Host: GitHub
- URL: https://github.com/usehenri/henri
- Owner: usehenri
- License: mit
- Created: 2016-01-13T17:23:59.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2023-01-06T01:46:12.000Z (over 2 years ago)
- Last Synced: 2024-11-16T01:11:36.285Z (5 months ago)
- Topics: disk, framework, graphql, hacktoberfest, henri, mongoose, nextjs, nodejs, react, react-server-render, server-side-rendering, vue
- Language: JavaScript
- Homepage: https://usehenri.io
- Size: 10.5 MB
- Stars: 51
- Watchers: 8
- Forks: 6
- Open Issues: 156
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# henri - the versatile javascript framework
[](https://www.npmjs.com/package/henri)
[](https://www.npmjs.com/package/henri)
[](https://travis-ci.org/usehenri/henri)
[](https://coveralls.io/github/usehenri/henri)
[](https://join.slack.com/t/usehenri/shared_invite/enQtNDU5Njg4MTA2OTY2LTU2N2I4MTVkNzQ4M2JlZTk5ZTIwODU1YTQxMzVmOTE2ZGVhZjNlZWY1ZTE2MWQxMDViZWY3ODY5ZjQwYzJiM2U)henri is an easy to learn rails-like, server-side rendered (react & vue) with powerful ORMs
- [How to use](#how-to-use)
- [Contributing](#contributing)
- [Configuration](#configuration)
- [Models](#models)
- [Disk](#disk)
- [MongoDB](#mongodb)
- [MySQL](#mysql)
- [MSSQL](#mssql)
- [PostgreSQL](#PostgreSQL)
- [GraphQL](#graphql)
- [Views](#views)
- [React](#react)
- [Inferno](#inferno)
- [Preact](#preact)
- [Vue.js](#vue)
- [Handlebars](#handlebars)
- [Fetching data again](#fetching-data-again)
- [Controllers](#controllers)
- [Routes](#routes)
- [Roles](#roles)
- [CRUD](#crud)
- [Resources](#resources)
- [Scope](#scope)
- [Mail](#mail)
- [Workers](#workers)
- [Under the hood](#under-the-hood)
- [Plans, plans!](#plans)## How to use
### Install
```bash
yarn global add henri# or
npm install -g henri
```### Create a new project
```bash
henri new
```The above command will create a directory structure similar to this:
```shell
├── app
│ ├── controllers
│ ├── helpers
│ ├── models
│ └── views
│ ├── assets
│ ├── components
│ ├── pages
│ ├── public
│ │ ├── css
│ │ ├── fonts
│ │ ├── img
│ │ ├── js
│ │ └── patterns
│ └── styles
├── config
│ ├── default.json
│ ├── production.json
│ ├── routes.js
│ └── webpack.js <- Overload Next.js webpack settings
├── test
│ ├── controllers
│ ├── helpers
│ ├── models
│ └── views
├── package.json
```If you have a _Ruby on Rails_ background, this might look familiar.
One last step to start coding is:
```bash
cd
henri server
```And you're good to go!
## Configuration
The configuration is a json file located in the `config` directory.
henri will try to load the file matching your `NODE_ENV` and will fallback to `default`.
You can have a `default.json`, `production.json`, etc.
```json
{
"stores": {
"default": {
"adapter": "mongoose",
"url": "mongodb://user:[email protected]:10914/henri-test"
},
"dev": {
"adapter": "disk"
}
},
"secret": "25bb9ed0b0c44cc3549f1a09fc082a1aa3ec91fbd4ce9a090b",
"renderer": "react"
}
```## Models
You can easily add models under `app/models`.
They will be autoloaded and available throughout your application (exposed globally).
We use [Mongoose](http://mongoosejs.com/) for MongoDB, [Sequelize](http://docs.sequelizejs.com/) for SQL adapters and [Waterline](https://github.com/balderdashy/waterline) for the disk adapter.
You can use the command-line to generate models:
```js
# henri g model modelname name:string! age:number notes:string birthday:date!
``````js
// app/models/User.js// Whenever you have a User model, it will be overloaded with the following:
// email: string
// password: string
// beforeCreate: encrypts the password
// beforeUpdate: encrypts the passwordmodule.exports = {
store: 'dev', // see the demo configuration up there
name: 'user_collection', // will use user_collection' instead of 'users'
schema: {
firstName: { type: 'string' },
lastName: String,
tasks: {},
},
};
``````js
// app/models/Tasks.jsmodule.exports = {
store: 'default', // see the demo configuration up there
schema: {
name: { type: 'string', required: true },
category: {
type: 'string',
validations: {
isIn: ['urgent', 'high', 'medium', 'low'],
},
defaultsTo: 'low',
},
},
};
```### Disk
The disk adapter is using [Waterline](https://github.com/balderdashy/waterline) to provide disk-based storage.
This is not for production and you can easily port your models to other adapters.
```bash
yarn add @usehenri/disk# or
npm install @usehenri/disk --save
```### MongoDB
The MongoDB adapter is using [Mongoose](http://mongoosejs.com/) to provide a MongoDB ODM.
```bash
yarn add @usehenri/mongoose# or
npm install @usehenri/mongoose --save
```### MySQL
The MySQL adapter is using [Sequelize](http://docs.sequelizejs.com/) to provide a MySQL ORM.
```bash
yarn add @usehenri/mysql# or
npm install @usehenri/mysql --save
```### MSSQL
The MSSQL adapter is using [Sequelize](http://docs.sequelizejs.com/) to provide a MSSQL ORM.
```bash
yarn add @usehenri/mssql# or
npm install @usehenri/mssql --save
```### PostgreSQL
The PostgresQL adapter is also using [Sequelize](http://docs.sequelizejs.com/) to provide a PostgresQL ORM.
```bash
yarn add @usehenri/postgresql# or
npm install @usehenri/postgresql --save
```## GraphQL
You can add a `graphql` key to your schema file and they will be automatically loaded, merged and available.
### Definition
```js
// app/models/Task.jsconst types = require('@usehenri/mongoose/types');
module.exports = {
schema: {
description: { type: types.STRING, required: true },
type: { type: types.ObjectId, ref: 'Type', required: true },
location: { type: types.ObjectId, ref: 'Location', required: true },
reference: { type: types.STRING, required: true },
notes: { type: types.STRING },
oos: { type: types.BOOLEAN, default: false },
},
options: {
timestamps: true,
},
graphql: {
types: `
type Task {
_id: ID!
reference: String!
description: String!
location: Location
type: Type
notes: String!
oos: Boolean
}
type Query {
tasks: [Task]
task(_id: ID!): Task
}
`,
resolvers: {
Query: {
tasks: async () => {
return Task.find()
.populate('type location')
.exec();
},
task: async (_, id) => await Task.findOne(id).populate('type'),
},
},
},
};
```### Query
You will be able to query this anywhere. Even as an argument to `res.render()`. See below:
```js
// app/controllers/tasks.js// henri has a gql function which does nothing but help editors parse gql...!
const { gql } = henri;module.exports = {
index: async (req, res) => {
return res.render('/tasks', {
graphql: gql`
{
tasks {
_id
reference
description
type {
_id
name
prefix
counter
}
location {
_id
name
}
}
locations {
_id
name
}
}
`,
});
},
};
```## Views
You can use [React](#react), [Vue](#vue) and [Handlebars](#handlebars) as renderer. They are all server-side rendered and the first two options use webpack to push updates to the browser.
### React
We use [next.js](https://github.com/zeit/next.js) to render pages and inject
data from controllers. You can only add pages and if the defined routes don't
match, and next matches a route, it will be rendered.Usage (config file):
```json
{
"renderer": "react"
}
```Example:
```jsx
// app/views/pages/log.jsimport React from 'react';
import Link from 'next/link';export default data => (
);
```You can also add webpack configuration in `config/webpack.js`:
```js
// If you want to have jQuery as a global...module.exports = {
webpack: async (config, { dev }, webpack) => {
config.plugins.push(
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
})
);
return config;
},
};
```#### Inferno
You can use Inferno instead of React in production. In development, React will be used for hot re/loading.
Installation:
```bash
yarn add react react-dom inferno inferno-compat inferno-server
```Usage (config file):
```json
{
"renderer": "inferno"
}
```#### Preact
You can use Preact instead of React in production. In development, React will be used for hot re/loading.
Installation:
```bash
yarn add react react-dom preact preact-compat
```Usage (config file):
```json
{
"renderer": "preact"
}
```### Vue.js
We use [Nuxt.js](https://nuxtjs.org/) to render pages and inject
data from controllers. You can only add pages and if the defined routes don't
match, and nuxt matches a route, it will be rendered.Usage (config file):
```json
{
"renderer": "vue"
}
```Example:
```vue
Welcome!
About page
```
### Handlebars
The handlebars options renders your `.html` or `.hbs` files under `app/views/pages`.
It will also load partials from `app/views/partials`
Usage (config file):
```json
{
"renderer": "template"
}
```Example:
```twig
Hello!
{{> somePartials }}
```
### Fetching data again
You can refetch data from any data-hydrated controller endpoint with GET using the `application/json` header.
## Controllers
You can easily add controllers under `app/controllers`.
They will be autoloaded and available throughout your application.
Controllers are auto-reloaded on save.
```js
// app/controllers/User.js
module.exports = {
info: async (req, res) => {
if (!req.isAuthenticated()) {
return res.status(403).send("Sorry! You can't see that.");
}
const { user } = henri;
if (await User.count({ email: '[email protected]' })) {
await User.update({ email: '[email protected]' }, { password: 'blue' });
return res.send('user exists.');
}
try {
await user.compare('moo', pass);
res.send('logged in!');
} catch (error) {
res.send('not good');
}
},
create: (req, res) => {
await User.create({ email: '[email protected]', password: 'moo' });
},
fetch: async (req, res) => {
const users = await User.find();
res.send(users);
},
postinfo: async (req, res) => {
let data = req.isAuthenticated() ? await User.find() : {};
res.render('/log', data);
}
};
```
## Routes
Routes are defined in `config/routes.js`. Also, any pages in `app/views/pages` will
be rendered if no routes match before.
Routes are a simple object with a key standing as a route or an action verb
(used by express) and a route.
If you want to access the `res.render` data, you can make the call with
`application/json` header. Everything else will be rendered.
```js
// config/routes.js
module.exports = {
'/test': 'user#info', // default to 'get /test'
'/abc/:id': 'moo#iii', // as this controller does not exists, route won't be loaded
'/user/find': 'user#fetch',
'get /poo': 'user#postinfo',
'post /poo': 'user#create',
'get /secured': {
controller: 'secureController#index',
roles: ['admin'],
},
'resources todo': {
controller: 'todo',
},
'crud categories': {
scope: 'api',
controller: 'categories',
omit: ['destroy'], // DELETE route will not be loaded
},
};
```
### Roles
You can specify an array of roles which need to be matched to access the routes.
### CRUD
The `crud` keyword (instead of http verbs) will create routes in a predefined way:
```js
// 'crud happy': 'life'
GET /happy => life#index
POST /happy => life#create
PATCH /happy/:id => life#update
PUT /happy/:id => life#update
DELETE /happy/:id => life#destroy
```
### Resources
The `resources` keyword (instead of http verbs) add views target to CRUD, ending up with:
```js
// 'resources happy': 'life'
GET /happy => life#index
POST /happy => life#create
PATCH /happy/:id => life#update
PUT /happy/:id => life#update
DELETE /happy/:id => life#destroy
GET /happy/:id/edit => life#edit
GET /happy/new => life#new
GET /happy/:id => life#show
```
### Scope
You can add `scope` to your routes to prefix them with anything you want.
### Omit (crud & resources only)
You can add `omit` array to your routes to prevent this route to be created.
We use [nodemailer](https://nodemailer.com) to provide email capabilities.
When running tests, we use nodemailer's ethereal fake-mail service.
### Config
```json
{
"mail": {
// ...Same as nodemailer's config
}
}
```
### Send
We provide a wrapper around `nodemailer.SendMail`:
```js
await henri.mail.send({
from: '"Henri Server" ', // sender address
to: '[email protected], [email protected]', // list of receivers
subject: 'Hello ✔', // Subject line
text: 'Hello world?', // plain text body
html: 'Hello world?', // html body
});
```
If you are using the test accounts, you will see a link to your email in the console.
You can access nodemailer's package directly from `henri.mail.nodemailer` and
transporter from `henri.mail.transporter`.
## Workers
You can add files under `app/workers` and they will be auto-loaded, watched and reloaded.
If they export a `start()` and a `stop()` method, they will be call when initializing and tearing down (reload also).
Example:
```js
let timer;
const start = h => {
h.pen.info('worker started');
timer = setInterval(
() => h.pen.warn(`the argument is the henri object`),
5000
);
};
const stop = () => clearInterval(timer);
module.exports = { start, stop };
```
## Under the hood
### Vision
_Bundle the best tools in a structured environment to provide a stable and fast-paced development experience._
### Modules
_We use a 8 levels boot system._
1. All modules are scanned and put in a sequence with same-level modules
2. We cycle from level 0 to 7, initializing all the same-level modules in a concurrent way
3. If the module is reloadable, it will unwind and rewind in the same sequence on reloads
See the [Contributing](#contributing) section for more information
## Plans
- Add helpers integration
- Add documentation!
- Build a website
- Report bugs!
## Contributing
- Step by step wiki [here](https://github.com/usehenri/henri/wiki/Contributing)
## Thanks to the following and their contributors
- [Next.js](https://github.com/zeit/next.js)
- [Express](https://expressjs.com/)
## Author
- Félix-Antoine Paradis ([@reel](https://github.com/reel))