# scripts
Live in the future, then build what's missing. - Paul Graham
Scripts are one of the pillars of Blix. With the blix scripts
command you can add a one of our scripts to an existing project or create a new custom script with an optional template.
For learning more about how scripts work check out our guide
Future Changes
Expect how Blix scripts work to undergo heavy changes in the future. We may decide to stop embedding the scripts in the project and keep optional templates, or switch to a yaml file, we're also considering a ML solution. Stay tuned.
# React Component Scripts
React component scripts come in several different types depending on the type of project you have. What they share in common is the ability to create a stateful or stateless component, inside it's own folder, and always with a css file.
Usage
blix generate component <name>
You will then be prompted via y/N cli question whether this component is stateful. If it's not a functional component is created.
Here are example of a "Button" component. We show stateful, stateless, and a container if the project uses Redux.
Stateful Component
import React, { Component } from 'react'
import './Button.css'
class Button extends Component {
constructor(props) {
super(props)
this.state = {}
}
render () {
return (
<div>
Hello Button
</div>
)
}
}
export default Button
Stateless Component
import React from 'react'
import './Button.css'
const Button = (props) => {
return(
<div>Hello Button</div>
)
}
export default Button
Redux Container
import { connect } from 'react-redux'
import Button from './Button'
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps, null)(Button)
# Basic React Component Script
Creates a folder in src
with the name provided. Inside the folder you'll find a file with the name uppercased and a empty css file with the same name.
# React-Router Component Script
Creates a folder in src/components
with the name provided. Inside the folder you'll find a file with the name uppercased and a empty css file with the same name.
# Redux Component Script
Creates a folder in src
with the name provided. Inside the folder you'll find a file with the name uppercased and a empty css file with the same name as well as a NameContainer.js redux container. We prefer to keep our container logic separate from our components.
# React-Router + Redux Component Script
Creates a folder in src/components
with the name provided. Inside the folder you'll find a file with the name uppercased and a empty css file with the same name as well as a NameContainer.js redux container. We prefer to keep our container logic separate from our components.
# Redux Action Script
Usage
blix generate action
You will then prompted:
- For the action's name.
- The Reducers name.
- A list of containers in the project and a question of "which containers should this action apply to".
Notes:
- If the Reducer already exists it will add the action to the reducer. If not it will create and import the new reducer into the rootReducer.
- You can add the action to more than one container at a time.
- The action will be imported into the container. If dispatch is not mapped it will be created and connected in the export.
- Container names are case sensitive.
Example
blix generate action
# action name: login
# reducer name: User
# containers to apply action to: Navbar
NavbarContainer.js
import { connect } from 'react-redux'
import { login } from '../../actions'
import Navbar from './Navbar'
const mapStateToProps = (state) => {
return state
}
const mapDispatchToProps = (dispatch) => {
return {
handleLogin: (input) => {
dispatch(login(input))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Navbar)
User Reducer
const user = (state = null, action) => {
switch (action.type) {
case 'LOGIN':
return action.payload
default:
return state
}
}
export default user
rootReducer.js
import { combineReducers } from 'redux'
import User from './User'
const rootReducer = combineReducers({
User
})
export default rootReducer
action
export const login = (payload) => {
return {
type: "LOGIN",
payload
}
}
This is an ultra powerful script.
# View Scripts
The view command creates a view component in src/views
, creates the route in the
router library file (src/Router.js
for React and src/router.js
for Vue).
It also will ask to import components (or Redux containers) into the view so you can quickly start to build the new page.
Usage
blix generate view <name>
# React-Router Example
React-Router scripts are fairly straightforward. It will create a new stateful or stateless component in src/views/
.
It will autumatically import the view into the src/Router.js
and insert it's route after which you can navigate to that route using React router links inside the views (although you may need to reload the page to ensure it updated 😉).
Components are listed by first level directory inside src/components
and currently are only imported into the new view (but will soon be able to be dropped exactly where you specify inside the new view!).
Create an About view
blix generate view about
# ? Please enter the url route to the view: about
# Is this view stateful (Y/n): y
# Components: [ 'Navbar' ]
# ? Which components should be used by this view: (you can enter more than one at a time, case-sensitive) Navbar
# Imported Navbar component into src/views/About.js
Creates a file src/views/About.js
About.js
import React, { Component } from 'react'
import Navbar from '../components/Navbar/Navbar'; // notice navbar was imported
class About extends Component {
constructor(props) {
super(props)
this.state = {}
}
render () {
return (
<div>
Hello About
</div>
)
}
}
export default About
Imports View into Router:
import React, {Component, Fragment} from 'react';
import {Route, Switch} from 'react-router-dom';
import Home from './views/Home';
import About from './views/About'
class Router extends Component {
render() {
return (
<Fragment>
<Switch>
<Route exact path='/about' render={(history) => {
return <About/>
}}/>
<Route exact path='/' component={Home} />
</Switch>
</Fragment>
)
}
}
export default Router
# Api Script
Creates a file with Axios functions for a resource. When run this will
create a file with the name of the resource plus Data.js. So a resource name of user will be userData.js.
For separation of concerns we provide the src/api
folder for all frontend projects.
Usage
blix generate api <resource>
This creates async await functions with try/catch routes for:
GET /api/v1/resource
PUT /api/v1/resource/:id
DELETE /api/v1/resource/:id
POST /api/v1/resource
Here's an example of a user resource file created by running blix generate api user
import axios from 'axios'
const getUser = async () => {
try {
let data = await axios.get('/api/v1/user')
return data
} catch (err) {
throw err
}
}
const postUser = async (user) => {
try {
let response = await axios.post('/api/v1/user', user)
return response
} catch (err) {
throw err
}
}
const putUser = async (user, id) => {
try {
let response = await axios.put(`/api/v1/user/${id}`, user)
return response
} catch (err) {
throw err
}
}
const deleteUser = async (id) => {
try {
let response = await axios.delete(`/api/v1/user/${id}`)
return response
} catch (err) {
throw err
}
}
export {
getUser,
postUser,
putUser,
deleteUser
}
If you don't want to use the /api/v1/ you can quickly edit this in the scripts/templates/api.js
file.
These routes map with our backend controller script in order to quickly connect client and server in a standardized way.
# Controller Script
Creates a file and adds routes to the server's routes. The standard Blix server is has a controllers
folder
and routes.js
file.
Usage
blix generate controller <resource>
Routes Generated in routes.js
GET /api/v1/resource
PUT /api/v1/resource/:id
DELETE /api/v1/resource/:id
POST /api/v1/resource
Controller Generated
// from an example of "blix generate controller user"
// so the file will be server/controllers/user.js
exports.get = (req, res) => { }
exports.put = (req, res) => { }
exports.deleteUser = (req, res) => { }
exports.post = (req, res) => { }
The functions inside the controller are already connected in the routes.js file.
# Model Scripts
A model script creates a database ORM model file in server/models
.
Currently we have model scripts for Mongoose and Bookshelf ORM's.
Either script can be run with just a model name and will create a base model file.
However, you can also pass in "fields" with the field type separated by a colon.
If no field type is provided it defaults to type "string". The accepted fields differ by database type and are listed below.
Basic Usage
blix generate model <name>
# Mongoose model script
In the Mongoose script the field type is capitalized to reflect the Mongoose class types.
Valid types: String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array
Create Mongoose User Model
blix generate model User name age:Number isAdmin:Boolean emails:Array
Creates a file server/models/User.js
.
let mongoose = require('mongoose')
let Schema = mongoose.Schema
let ObjectId = Schema.ObjectId
let userSchema = new Schema({
name: String,
age: Number,
isAdmin: Boolean,
emails: Array
})
module.exports = mongoose.model('User', userSchema)
# Bookshelf model script
The Bookshelf model script has a couple of extra actions to note.
In order to work we must instantiate a bookshelf instance, and in order to do so we create a bookshelf.js file in server/models/
.
When run the script will also create a migration file in db/migrations
.
The field types are snakecased (unlike other scripts like the Mongoose script which takes uppercase field names).
Valid types: string, binary, boolean, date, dateTime, decimal, float, integer, bigInteger, text, json, jsonb
Create Bookshelf User Model and Migration
blix generate model User name age:integer is_admin:boolean emails:integer
Creates model file: server/models/User.js
let bookshelf = require('./bookshelf')
let User = bookshelf.Model.extend({
tableName: 'users'
})
module.exports = User
Creates migration: db/migrations/20181202233921_users.js
(the file id will be different)
exports.up = (knex, Promise) => {
return Promise.all([
knex.schema.createTable('users', (t) => {
t.increments('id').primary()
t.string('name')
t.integer('age')
t.boolean('is_admin')
t.integer('emails') // this will reference a foreign key
t.timestamps(true, true)
})
])
}
exports.down = (knex, Promise) => {
return Promise.all([
knex.schema.dropTable('users')
])
}
If you encounter errors we recommend running adding a database via blix add
and trying again.
# Creating a Custom Script
This is currently being refactored