Difference between revisions of "User:Saul/Realtime app"

From Organic Design wiki
(Manual: Added two more files that are often needed.)
(Auto: Extended command to create two of the lib files as well.)
Line 93: Line 93:
 
*rewrites main.js to use the vue-router and vuex
 
*rewrites main.js to use the vue-router and vuex
 
<source lang="bash">
 
<source lang="bash">
echo -e "import Vue from \"vue\"\nimport Vuex from \"vuex\"\n//import chat from \"./SOTRENAMEONE\"\n//import login from \"./SOTRENAMETWO\"\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n\tmodules: {\n\t\t/*\n\t\t\tchat,\n\t\t\tlogin\n\t\t*/\n\t}\n})" > src/store/index.js && echo -e "import Vue from \"vue\"\nimport Router from \"vue-router\"\n//import Login from \"./pages/Login.vue\"\n//import Register from \"./pages/Register.vue\"\n//import Chat from \"./pages/Chat.vue\"\n//import auth from \"./libs/auth\"\n\nVue.use(Router)\n\nconst router = new Router({\n\tmode: \"history\",\n\troutes: [\n\t\t/*\n\t\t\t{\n\t\t\t\tpath: \"/\",\n\t\t\t\tname: \"Chat\",\n\t\t\t\tcomponent: Chat,\n\t\t\t\tbeforeEnter: auth.isLoggedIn\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/login\",\n\t\t\t\tname: \"Login\",\n\t\t\t\tcomponent: Login\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/register\",\n\t\t\t\tname: \"Register\",\n\t\t\t\tcomponent: Register\n\t\t\t},\n\t\t*/\n\t\t// If access a non-existing route, redirect to root route\n\t\t{\n\t\t\tpath: \"*\",\n\t\t\tredirect: \"/\"\n\t\t}\n\t]\n})\n\nrouter.afterEach(function (to, from){\n\t//Set the title for the pages\n\tdocument.title = to.name;\n});\n\nexport default router;" > src/router.js && echo -e "<template>\n\t<div id=\"app\">\n\t\t<router-view></router-view>\n\t</div>\n</template>\n\n<script>\n\texport default {\n\t\tname: \"app\"\n\t}\n</script>\n\n<style>\n\t#app, body, html{\n\t\tbox-sizing: border-box;\n\t\tpadding: 0px;\n\t\tmargin: 0px;\n\t}\n</style>" > src/App.vue && echo -e "import Vue from \"vue\"\nimport App from \"./App.vue\"\nimport router from \"./router\"\nimport store from \"./store\"\n\nnew Vue({\n\tel: \"#app\",\n\tstore,\n\trouter,\n\trender: h => h(App)\n})" > src/main.js
+
echo -e "import Vue from \"vue\"\nimport Vuex from \"vuex\"\n//import chat from \"./SOTRENAMEONE\"\n//import login from \"./SOTRENAMETWO\"\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n\tmodules: {\n\t\t/*\n\t\t\tchat,\n\t\t\tlogin\n\t\t*/\n\t}\n})" > src/store/index.js && echo -e "import Vue from \"vue\"\nimport Router from \"vue-router\"\n//import Login from \"./pages/Login.vue\"\n//import Register from \"./pages/Register.vue\"\n//import Chat from \"./pages/Chat.vue\"\n//import auth from \"./libs/auth\"\n\nVue.use(Router)\n\nconst router = new Router({\n\tmode: \"history\",\n\troutes: [\n\t\t/*\n\t\t\t{\n\t\t\t\tpath: \"/\",\n\t\t\t\tname: \"Chat\",\n\t\t\t\tcomponent: Chat,\n\t\t\t\tbeforeEnter: auth.isLoggedIn\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/login\",\n\t\t\t\tname: \"Login\",\n\t\t\t\tcomponent: Login\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/register\",\n\t\t\t\tname: \"Register\",\n\t\t\t\tcomponent: Register\n\t\t\t},\n\t\t*/\n\t\t// If access a non-existing route, redirect to root route\n\t\t{\n\t\t\tpath: \"*\",\n\t\t\tredirect: \"/\"\n\t\t}\n\t]\n})\n\nrouter.afterEach(function (to, from){\n\t//Set the title for the pages\n\tdocument.title = to.name;\n});\n\nexport default router;" > src/router.js && echo -e "<template>\n\t<div id=\"app\">\n\t\t<router-view></router-view>\n\t</div>\n</template>\n\n<script>\n\texport default {\n\t\tname: \"app\"\n\t}\n</script>\n\n<style>\n\t#app, body, html{\n\t\tbox-sizing: border-box;\n\t\tpadding: 0px;\n\t\tmargin: 0px;\n\t}\n</style>" > src/App.vue && echo -e "import Vue from \"vue\"\nimport App from \"./App.vue\"\nimport router from \"./router\"\nimport store from \"./store\"\n\nnew Vue({\n\tel: \"#app\",\n\tstore,\n\trouter,\n\trender: h => h(App)\n})" > src/main.js && echo -e "import defaultConf from \"../../config/default.json\"\nimport productionConf from \"../../config/production.json\"\nimport merge from \"lodash.merge\"\n\nlet conf = merge({}, defaultConf)\n\n// When we build for production, merge production conf\nif (process.env.NODE_ENV === \"production\") {\n\tconf = merge(conf, productionConf)\n}\n\nexport default conf" > src/libs/conf.js && echo -e "const io = require(\"socket.io-client\");\nconst feathers = require(\"@feathersjs/feathers\");\nconst socketio = require(\"@feathersjs/socketio-client\");\nconst auth = require(\"@feathersjs/authentication-client\");\nconst client = feathers();\nimport conf from \"./conf\";\n\nconst socket = io(\`http://\${conf.host}:\${conf.port}\`, {\n\ttransports: [\"websocket\"],\n\tforceNew: true\n});\n\nimport store from \"../store\";\n\nclient.configure(auth({ storage: localStorage }));\n// Create the Feathers application with a \`socketio\` connection\nclient.configure(socketio(socket));\n\nexport default client;" > src/libs/client.js
 
</source>
 
</source>
  

Revision as of 21:25, 21 March 2018

This is the documentation for the stack I use for creating a real-time application. The stack consists of node.js and feathers.js for the server, the client uses: Vue, vue-router, vuex and setup using the vue webpack.

Setup

Install And Generate Files

The following commands will setup the app:

sudo npm install -g vue @feathersjs/cli # install dependencies globally
sudo vue init webpack-simple APPNAME # create a new project using the "webpack-simple" template
mkdir APPNAME/server && cd APPNAME/server
feathers generate app # generate the feathers app

Modify Files

A few changes are need to finish connecting the app:

Copy all dependencies from server/package.json to the package.json located in the root dir. Do the same for request and request-promise from devDependencies.

Add to the scripts section in package.json (this allows the back end to be started via "npm run server"):

"server": "node server/src/",

Add to package.json:

"directories": {
	"lib": "server/src"
},

Modify the dev script in package.json to:

"cross-env NODE_ENV=development webpack-dev-server --content-base server/public --open --hot"

Modify the config/default.json file like so:

"public": "../server/public/",

Modify the webpack.config.js by changing the path and publicPath like so:

path: path.resolve(__dirname, "server", "public"),
publicPath: "/",

Remove the line in webpack.config.js which makes babel-loader exclude the node module folders.
This appears to make build crash for some reason, perhaps feathers.js is written in es6, when it should be written in es5?

// modify this section like so:
test: /\.js$/,
loader: 'babel-loader'/*,
exclude: /node_modules/*/

Modify the index.html so that the script build source is:

<script src="/build.js"></script>

Fix Structure, Install, And Cleanup

These commands will finish arranging the files (make sure you are still in the server dir):

mv config ../config && mv .editorconfig ../.editorconfig && mv ../index.html public/index.html # move then files to the proper dirs
rm package.json LICENSE .gitignore .npmignore README.md .eslintrc.json package-lock.json -R test node_modules # delete the unnecessary files

And to install the dependencies and run:

cd .. # go back to the project root
npm i @feathersjs/client vuex vue-router # install additional dependencies
npm i # install dependencies

Run App

To run in development:

npm run dev # this launches the front-end
npm run server # this launches the back-end

To run in production:

npm build # build the front-end into the backend
npm run server # run the backend

See Also

Client

File Structure

To generate the recommended file structure, navigate to the project root directory and run the following command:

mkdir src/components src/store src/libs src/pages # generate the client end folders: components (for storing vue components), store (for storing vuex store files), and libs (for storing client side js libs)

Auto

To automatically edit the files run this one-liner or skip to manual if you would like to manually edit them. The code does the following:

  • generates the src/store/index.js file with an example included.
  • generates the src/router.js file with examples
  • rewrites App.vue to use the vue-router
  • rewrites main.js to use the vue-router and vuex
echo -e "import Vue from \"vue\"\nimport Vuex from \"vuex\"\n//import chat from \"./SOTRENAMEONE\"\n//import login from \"./SOTRENAMETWO\"\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n\tmodules: {\n\t\t/*\n\t\t\tchat,\n\t\t\tlogin\n\t\t*/\n\t}\n})" > src/store/index.js && echo -e "import Vue from \"vue\"\nimport Router from \"vue-router\"\n//import Login from \"./pages/Login.vue\"\n//import Register from \"./pages/Register.vue\"\n//import Chat from \"./pages/Chat.vue\"\n//import auth from \"./libs/auth\"\n\nVue.use(Router)\n\nconst router = new Router({\n\tmode: \"history\",\n\troutes: [\n\t\t/*\n\t\t\t{\n\t\t\t\tpath: \"/\",\n\t\t\t\tname: \"Chat\",\n\t\t\t\tcomponent: Chat,\n\t\t\t\tbeforeEnter: auth.isLoggedIn\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/login\",\n\t\t\t\tname: \"Login\",\n\t\t\t\tcomponent: Login\n\t\t\t},\n\t\t\t{\n\t\t\t\tpath: \"/register\",\n\t\t\t\tname: \"Register\",\n\t\t\t\tcomponent: Register\n\t\t\t},\n\t\t*/\n\t\t// If access a non-existing route, redirect to root route\n\t\t{\n\t\t\tpath: \"*\",\n\t\t\tredirect: \"/\"\n\t\t}\n\t]\n})\n\nrouter.afterEach(function (to, from){\n\t//Set the title for the pages\n\tdocument.title = to.name;\n});\n\nexport default router;" > src/router.js && echo -e "<template>\n\t<div id=\"app\">\n\t\t<router-view></router-view>\n\t</div>\n</template>\n\n<script>\n\texport default {\n\t\tname: \"app\"\n\t}\n</script>\n\n<style>\n\t#app, body, html{\n\t\tbox-sizing: border-box;\n\t\tpadding: 0px;\n\t\tmargin: 0px;\n\t}\n</style>" > src/App.vue && echo -e "import Vue from \"vue\"\nimport App from \"./App.vue\"\nimport router from \"./router\"\nimport store from \"./store\"\n\nnew Vue({\n\tel: \"#app\",\n\tstore,\n\trouter,\n\trender: h => h(App)\n})" > src/main.js && echo -e "import defaultConf from \"../../config/default.json\"\nimport productionConf from \"../../config/production.json\"\nimport merge from \"lodash.merge\"\n\nlet conf = merge({}, defaultConf)\n\n// When we build for production, merge production conf\nif (process.env.NODE_ENV === \"production\") {\n\tconf = merge(conf, productionConf)\n}\n\nexport default conf" > src/libs/conf.js && echo -e "const io = require(\"socket.io-client\");\nconst feathers = require(\"@feathersjs/feathers\");\nconst socketio = require(\"@feathersjs/socketio-client\");\nconst auth = require(\"@feathersjs/authentication-client\");\nconst client = feathers();\nimport conf from \"./conf\";\n\nconst socket = io(\`http://\${conf.host}:\${conf.port}\`, {\n\ttransports: [\"websocket\"],\n\tforceNew: true\n});\n\nimport store from \"../store\";\n\nclient.configure(auth({ storage: localStorage }));\n// Create the Feathers application with a \`socketio\` connection\nclient.configure(socketio(socket));\n\nexport default client;" > src/libs/client.js

Manual

This section does the same thing manually as Auto does.

First create the src/store/index.js file like this:

import Vue from "vue"
import Vuex from "vuex"
//import chat from "./SOTRENAMEONE"
//import login from "./SOTRENAMETWO"

Vue.use(Vuex)

export default new Vuex.Store({
	modules: {
		/*
			chat,
			login
		*/
	}
})

Then create the src/router.js file with the following:

import Vue from "vue"
import Router from "vue-router"
//import Login from "./pages/Login.vue"
//import Register from "./pages/Register.vue"
//import Chat from "./pages/Chat.vue"
//import auth from "./libs/auth"

Vue.use(Router)

const router = new Router({
	mode: "history",
	routes: [
		/*
			{
				path: "/",
				name: "Chat",
				component: Chat,
				beforeEnter: auth.isLoggedIn
			},
			{
				path: "/login",
				name: "Login",
				component: Login
			},
			{
				path: "/register",
				name: "Register",
				component: Register
			},
		*/
		// If access a non-existing route, redirect to root route
		{
			path: "*",
			redirect: "/"
		}
	]
})

router.afterEach(function (to, from){
	//Set the title for the pages
	document.title = to.name;
});

export default router;

Then change the contents of App.vue like so:

<template>
	<div id="app">
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name: "app"
	}
</script>

<style>
	#app, body, html{
		box-sizing: border-box;
		padding: 0px;
		margin: 0px;
	}
</style>

Change the contents main.js to use the vue-router and vuex like this:

import Vue from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"

new Vue({
	el: "#app",
	store,
	router,
	render: h => h(App)
})

Create the client conf data lib as src/libs/conf.js:

import defaultConf from "../../config/default.json"
import productionConf from "../../config/production.json"
import merge from "lodash.merge"

let conf = merge({}, defaultConf)

// When we build for production, merge production conf
if (process.env.NODE_ENV === "production") {
  conf = merge(conf, productionConf)
}

export default conf

Finally create the feathers lib as src/libs/client.js:

const io = require('socket.io-client');
const feathers = require('@feathersjs/feathers');
const socketio = require('@feathersjs/socketio-client');
const auth = require('@feathersjs/authentication-client');
const client = feathers();
import conf from "./conf";

const socket = io(`http://${conf.host}:${conf.port}`, {
  transports: ['websocket'],
  forceNew: true
});

import store from "../store";

client.configure(auth({ storage: localStorage }));
// Create the Feathers application with a `socketio` connection
client.configure(socketio(socket));

export default client

Creating Pages

For every page you would like to make create a file in src/pages/PAGENAME.vue and then add it in the src/router.js file.
Each page should have the standard vue template:

<template>
</template>

<script>
</script>

<style>
</style>

Creating Components

Components are independent pieces of code to be embedded into a page.
For every component you would like to make create a file in src/component/COMPONENTNAME.vue
Each component should have the standard vue template (like pages do).
To embed the components in a page you need to import them in the page (in the script section):

import Chat from './components/chat.vue'
import Channels from './components/channels.vue'

export default{
	components: {
		Channels,
		Chat
	}
}

Then you can use them in the template section like so:

<channels></channels>
<chat></chat>

Creating Vuex Stores

Vuex stores are ways of preserving state.
To create a vuex store for your application create the folder src/store/STORENAME
Create a index.js file in the src/store/STORENAME folder. The index.js file should contain something like:

export const state = {
	// This should contain variables that you would like to preserve
}

export const mutations = {
	// This should contain methods for modifying the state section but should not be called directly
}

export const actions = {
	// This should contain methods that do not modify the state directly
	// They may however modify the stat by calling a method in the mutations
	// This may be done via: commit("mutation_name", channel)
}

export const getters = {
	// This should contain methods for retrieving the data from the state
}

export default {
  state,
  mutations,
  actions,
  getters
}

Then you need to modify the src/store/index.js to reference it.
Then you can import it into your pages and use the data.

Using JS Librarys

To add a JS library just create the file src/libs/LIBNAME.js
Add the code into that file and import it into the relevant files.

Server

Routing

Some modifications need to be made so the server understands how to deliver content.
If you try to go to localhost:3030/somepagename you will get a 404 error.
So to fix this add below the public folder host route the following code:

// This line already exists:
app.use('/', express.static(app.get('public')));
// Add these 3 lines:
app.get('/*',  function(req,res){
	res.sendFile(path.join(app.get('public'), 'index.html'));
});

This will make all routes return the index.html file but keep the route for vue to handle.

Services

To generate a feathers js service run:

feathers generate service # Creates the feathers.js service
# Example: Mongoose, chat, /chat, mongodb://localhost:27017/chat

If you have issues with a service check what the service name is.
To generate the authentication service run:

feathers generate authentication # Creates the feathers.js authentication service

Authentication automatically creates the necessary hooks for protecting users passwords.
The authentication will also create the channels.js which sets up publishing for channels.