I want to write a few posts on express examples that are actual full working application examples, rather than just simple hello world type examples. There is of course the typical todo app that is often the case, but I want to make a few more that go beyond that into other examples as well. As of late I have been transitioning from using windows to linux, and so far have been having a hard time finding a text editor that stacks up to notepad++ which I have grown accustomed to in windows. So why not make my own text editor on top of node.js, and express that I can take with me to any operating system that I can get node.js installed on? Sounds like a good idea to me compared to being dependent on a windows exclusive app, so I put together a quick basic expressjs powered text editor example.
1 - What to know about this Express Example before hand
For the sake of this post I am thinking more in terms of an express example that is starting to look like an actual project of one sort or another rather than a more basic example. If you are new to express you might want to start with my getting started post on express, and also my main post on express as well. I also assume that you have loged a fair amount of time playing around with javaScript and node.js in general, I will not be getting into that or anything else that is outside the scope of this post.
1.1 - Setup
For this project I am just using express when it comes to node.js dependencies, and I am just using a vanilla js client system. In the root of the project folder I have a folder for express middleware, and a public folder for all assets the compose the client system.
1
2
3
4
5
6
7
8
9
$ mkdir express-example-text-editor
$ cd express-example-text-editor
$ npm init
$ npm install express@4.16.4 --save
$ mkdir middleware
$ mkdir public
$ cd public
$ mkdir html
$ mkdir js
2 - The /app.js file
So in the main app.js file in the root of the project folder I am just using express to create an instance of an app object. I am also using the built in path module as well here. The app.set method is then used to set some application settings for port, the current working directory and so forth. I then use express static to host static assets in the public folder that will compose the client system, and I am also using the built in body parser middleware to parse incoming json from the client system as well.
app.listen(app.get('port'), () => console.log('example text editor is up on port: ' + app.get('port')));
I am then using a bunch of middleware methods that I have made and placed in my middleware folder. These preform all kinds of actions when it such as getting the current file, saving the current file, and listing the contents of the current directory.
3 - The /middleware folder
The middleware folder is where I offset much of the logic for getting the current file, saving the current file, and checking for a proper request body and so forth. By breaking things down into fine grain methods and files, it helps to keep things better structured.
3.1 - /middleware/check_body.js
Here I have my body check middleware in this file I am just checking for the presence of a request body, and creating an object that will be a reply for all post requests made from the client system to the path that this middleware is mounted to. In the event that a body is not present a 400 status is sent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let path = require('path'),
fs = require('fs');
// create reply object, and check for body
module.exports = [
(req, res, next) => {
// Create reply object
res.reply = {
success: false,
mess: 'no body object populated.',
};
// check for body or next
if (!req.body) {
res.status(400).json(res.reply);
} else {
// sync server side fn and dir settings to any settings given by client
In the event that a request body is there, application settings will be updated here if values are given. In addition if not action property is given that will also send a 400 status as well. All post requests sent to the path should have an action property that will be used to know which action should be preformed.
3.2 - /middleware/action_open.js
Here I have my open action middleware this is what will be used to open the current file, and send the data of that file to the client system. A 400 status will be sent in the event that any kind of error will happen in the process of doing so.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let path = require('path'),
fs = require('fs');
// if action : 'open'
module.exports = (req, res, next) => {
if (req.body.action === 'open') {
// try to open the current filename at the current dir
The error message will be sent as part of the standard reply object so if something goes wrong that information can be displayed in the client system.
3.3 - /middleware/action_save.js
Here I have my save action middleware as the name suggests this is what will be used to save the current file with data that is sent from the client system via a post request body.
Again 400 status will be sent for any error that might happen while saving the file, or if no data is given to save.
3.4 - /middleware/action_list.js
Here I have my list middleware this is what I am using to create a list of the contents of the current directory. I am using the fs.readdir file system module method to read the contents of the current directory set in the application settings of the project. I am also using the fs.stat method to find out if an item in a directory is a file or another directory. This data is then sent to the client system that uses the data to implement a file system navigation feature of the text editor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
let path = require('path'),
fs = require('fs');
// if action : 'list' - to list files in current dir
That concludes the middleware that I am using, as well as all the back end code as well. Now for the public folder that contains all the front end code.
4 - The /public folder
The public folder contains html, css, and javaScript files. For this example there is nothing fancy going on in terms of the client system when it comes to the use of front end frameworks, as I did not want to pull to much attention away from express.
4.1 - /public/html/index.html
Here I have just the one html file that I am using for this project. I am using a textarea element for the text as this project is just a plain old text editor, so I do not need to do anything fancy when it comes to styling text.
Here I have another module that I worked out that has to do with making all the various requests to the express middleware back ends that I put together.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Module to help work with the
// back end system for the editor
var Menu = (function () {
// set / clear messages
var mess = (function () {
var el_mess = get('text_mess'),
el_eMess = get('text_emess');
var func = function (mess) {
el_mess.innerHTML = mess;
};
func.eMess = function (eMess) {
el_eMess.innerHTML = eMess;
};
func.clear = function () {
el_mess.innerHTML = '';
el_eMess.innerHTML = '';
};
return func;
}
());
// set the dir and fn input elements values to
// what is in the given reply object
var setInputs = function (reply) {
get('text_dir').value = reply.dir;
get('text_fn').value = reply.fn;
};
// public api
var api = {};
api.noop = function () {};
api.done = function (text) {
console.log(text)
};
api.error = function (eMess) {
console.log(eMess);
mess.eMess(eMess);
}
// Open the file that is at the current
// dir and fn app settings
api.Open = function (opt) {
// if null for dir or fn the default will
// be whatever is set server side
opt = opt || {};
mess.clear();
get({
payload: {
action: 'open',
dir: opt.dir || null,
fn: opt.fn || null
},
onDone: function (text, resObj) {
get('text_edit').value = text;
//get('text_fn').value = resObj.fn;
mess(resObj.mess);
setInputs(resObj);
},
onError: api.error
});
};
api.Save = function (opt) {
opt = opt || {};
mess.clear();
get({
payload: {
action: 'save',
dir: opt.dir || null,
fn: opt.fn || null,
data: get('text_edit').value
},
onDone: function (text, resObj) {
get('text_edit').value = text;
mess(resObj.mess);
},
onError: api.error
});
};
var emptyList = function () {
var list = get('list_files').contentWindow.document.body;