MEAN Stack Application Structure - MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

MEAN Stack Application Structure

Up to this point, we’ve been able to place most of our code into a few main files (server.js for Node and app.js for Angular). As we start moving to larger MEAN stack applications, keeping all of our code bunched into these main files will become cumbersome. It will be hard to maintain our code, find certain parts of our applications, and worst of all, share convoluted code with other developers.

With this in mind, let’s take a look at our application structure. Organizing something this simple goes a long way. This will be a short chapter, but a very important one.

Sample Organization

So far, our applications have had this file structure:

1 // node applications

2 - app/

3 ----- models/

4

5 // angular applications

6 - public/

7 ----- css/

8 ----- js/

9 ---------- controllers/

10 ---------- services/

11 ----- app.js

12 ----- views/

13 ---------- pages/

14 ----- index.html

15

16 - package.json

17 - server.js

On the Node side of things, we can’t keep everything in server.js. That file could become a giant spaghetti mix of code when we start putting more routes, more configuration, and more application functionality. That has to be broken down into a more organized structure.

On the Angular side of things, this structure could become confusing. Where would we put lib files like Bootstrap, or Font Awesome? Would our js folder hold all of those things including our Angular application files?

A Better Structure

So what would a better structure look like? Let’s combine both the Node and Angular sides since we will be moving towards full stack applications next.

Here is a better organization that lays a good foundation for our applications. This is a modular approach that allows us to add and remove components from our applications easily and simply.

1 - app/

2 ----- models/

3 ----- routes/

4 - public/

5 ----- assets/ // the base css/js/images for our app (not Angular files)

6 ---------- css/ // some custom css

7 ---------- js/ // some custom js (not Angular files)

8 ---------- img/

9 ---------- libs/ // libraries like bootstrap, angular, font-awesome

10 ----- app/ // the Angular part of our application

11 ---------- controllers/

12 ---------- services/

13 ---------- app.js

14 ---------- app.routes.js

15 ---------- views/

16 --------------- pages/

17 --------------- index.html

18 - package.json

19 - config.js- server.js

Let’s break down what is happening here. A lot of it is just moving things around into its own file/folder.

App Folder

We have our normal models folder here. What’s new is that we have a routes/ folder now. We can place our basic routes here and call them from our main server.js file. Since the routes are the main part of server.js, moving those out of the main file will clean it up substantially.

We can even create multiple routes files like routes/app.js and routes/api.js so that we can differentiate the parts of our application.

config.js

We are just moving configuration variables into this file. This helps since we can set variables like environment, database settings, and other application specific settings.

We can even grab these settings from a dashboard in the future so that we can have a dashboard UI where we can have users log in to set these.

Public Folder

This is where the majority of application movement has happened. Before, we stored everything (Angular files and basic asset files) in the public/js folder. This can become confusing as our application grows so now we have it set up so that assets and application files live in separate directories.

Anything Angular specific like controllers, services, routes, and views will live in the app/ folder. Assets like images, custom CSS or JS, and libraries will live in the assets/ folder.

Here we have also created a app.routes.js file. In the future as we create more routes for our application, we may want to move these into its own routes/ folder like we did for the Node side.

Now that we have seen a better structure, let’s take our Chapter 10 application and arrange it so that it adheres to this practice. This will lay the groundwork for moving forward when we create the User CRM.

Organizing Node.js - Backend

Since Chapter 10 was about creating a Node API and authenticating it, we only have Node code to work with here.

Let’s see how we can rearrange our Node code (say that 10 times fast… Node code, Node code, Node code). Go ahead and copy your Chapter 10 code and create a new project for it.

Here is an overview of the steps we will be taking:

· move configuration variables into a new config.js

· move routes into a new routes/api.js file

· create a catchall route to get ready for MEAN app

Configuration Files

We will be moving our configuration variables out of our server.js. For our purposes, we don’t have many configuration variables, just our port, our database, and the secret that we will be using when configuring JSON Web Tokens.

Create a new file in the root directory called config.js.

The following is all we need:

1 module.exports = {

2 'port': process.env.PORT || 8080,

3 'database': 'mongodb://node:noder@novus.modulusmongo.net:27017/Iganiq8o',

4 'secret': 'ilovescotchscotchyscotchscotch'

5 };

Now we can use this file inside of our server.js file using require(). The following lines will be edited:

1 var config = require('./config');

2

3 // connect to our database (hosted on modulus.io)

4 mongoose.connect(config.database);

5

6 // START THE SERVER

7 // ====================================

8 app.listen(config.port);

9 console.log('Magic happens on port ' + config.port);

And the following lines have been moved out, so can now be deleted from ‘server.js’:

1 // super secret for creating tokens

2 var superSecret = 'ilovescotchscotchyscotchscotch';

While this may not seem like it did a lot, the concept will go a long way when we have over 20+ settings for our application.


Note

Note

What is module.exports?

By default, JavaScript doesn’t have a way to pass information between different files. module.exports is Node’s way of fixing this problem.

You can think of module.exports as a giant object for our application. When our application starts, this module.exports object looks like this:

1 module.exports = {};

As we start pulling things into our app with require(), everything is added to this object.

So now as we add that new config.js file with require(./config.js);, we have access to those variables since we passed them through module.exports.

This is how we will be passing information to and from all of our files and we’ll see this in practice again when we create our routes files.

It is also important to note that many tutorials on the web will switch between module.exports and exports. They mean the same thing and can be used interchangeably.


Routes

Just like our configuration variables, let’s move our routes into their own files. Create a folder called app/routes/ and a file called app/routes/api.js.

When creating new files, we will have to require() anything that is needed by that file. In our case, we will need body-parser, our User model, our config file for the secret, and jsonwebtoken since those were used in our routes.

We will also need app and express but we will be passing those into our routes since we want our application to use the same app object we create in server.js. Let’s take a look at how we’ll structure our routes/api.js file that holds all of our API routes. Go into your existing ‘server.js’ file from chapter 10 and grab out all of the route code, starting at ‘var apiRouter = express.Router();’. We will be wrapping this in our module.exports so that it can be passed between our javascript files. Also take note of the changes to our variable superSecret and the packages we are including at the top.

1 var User = require('../models/user');

2 var jwt = require('jsonwebtoken');

3 var config = require('../../config');

4

5 // super secret for creating tokens

6 var superSecret = config.secret;

7

8 module.exports = function(app, express) {

9

10 var apiRouter = express.Router();

11

12 // route to authenticate a user (POST http://localhost:8080/api/authenticate)

13 apiRouter.post('/authenticate', function(req, res) {

14 console.log(req.body.username);

15

16 // find the user

17 // select the password explicitly since mongoose is not returning it by defa\

18 ult

19 User.findOne({

20 username: req.body.username

21 }).select('password').exec(function(err, user) {

22

23 if (err) throw err;

24

25 // no user with that username was found

26 if (!user) {

27 res.json({

28 success: false,

29 message: 'Authentication failed. User not found.'

30 });

31 } else if (user) {

32

33 // check if password matches

34 var validPassword = user.comparePassword(req.body.password);

35 if (!validPassword) {

36 res.json({

37 success: false,

38 message: 'Authentication failed. Wrong password.'

39 });

40 } else {

41

42 // if user is found and password is right

43 // create a token

44 var token = jwt.sign(user, superSecret, {

45 expiresInMinutes: 1440 // expires in 24 hours

46 });

47

48 // return the information including token as JSON

49 res.json({

50 success: true,

51 message: 'Enjoy your token!',

52 token: token

53 });

54 }

55

56 }

57

58 });

59 });

60

61 // route middleware to verify a token

62 apiRouter.use(function(req, res, next) {

63 // do logging

64 console.log('Somebody just came to our app!');

65

66 // check header or url parameters or post parameters for token

67 var token = req.body.token || req.param('token') || req.headers['x-access-to\

68 ken'];

69

70 // decode token

71 if (token) {

72

73 // verifies secret and checks exp

74 jwt.verify(token, superSecret, function(err, decoded) {

75 if (err) {

76 return res.json({ success: false, message: 'Failed to authenticate tok\

77 en.' });

78 } else {

79 // if everything is good, save to request for use in other routes

80 req.decoded = decoded;

81

82 next(); // make sure we go to the next routes and don't stop here

83 }

84 });

85

86 } else {

87

88 // if there is no token

89 // return an HTTP response of 403 (access forbidden) and an error message

90 return res.status(403).send({

91 success: false,

92 message: 'No token provided.'

93 });

94

95 }

96

97 });

98

99 // test route to make sure everything is working

100 // accessed at GET http://localhost:8080/api

101 apiRouter.get('/', function(req, res) {

102 res.json({ message: 'hooray! welcome to our api!' });

103 });

104

105 // on routes that end in /users

106 // ----------------------------------------------------

107 apiRouter.route('/users')

108

109 // create a user (accessed at POST http://localhost:8080/users)

110 .post(function(req, res) {

111

112 var user = new User(); // create a new instance of the User model

113 user.name = req.body.name; // set the users name (comes from the requ\

114 est)

115 user.username = req.body.username; // set the users username (comes f\

116 rom the request)

117 user.password = req.body.password; // set the users password (comes f\

118 rom the request)

119

120 user.save(function(err) {

121 if (err) res.send(err);

122

123 // return a message

124 res.json({ message: 'User created!' });

125 });

126

127 })

128

129 // get all the users (accessed at GET http://localhost:8080/api/users)

130 .get(function(req, res) {

131 User.find(function(err, users) {

132 if (err) res.send(err);

133

134 // return the users

135 res.json(users);

136 });

137 });

138

139 // on routes that end in /users/:user_id

140 // ----------------------------------------------------

141 apiRouter.route('/users/:user_id')

142

143 // get the user with that id

144 .get(function(req, res) {

145 User.findById(req.params.user_id, function(err, user) {

146 if (err) res.send(err);

147

148 // return that user

149 res.json(user);

150 });

151 })

152

153 // update the user with this id

154 .put(function(req, res) {

155 User.findById(req.params.user_id, function(err, user) {

156

157 if (err) res.send(err);

158

159 // set the new user information if it exists in the request

160 if (req.body.name) user.name = req.body.name;

161 if (req.body.username) user.username = req.body.username;

162 if (req.body.password) user.password = req.body.password;

163

164 // save the user

165 user.save(function(err) {

166 if (err) res.send(err);

167

168 // return a message

169 res.json({ message: 'User updated!' });

170 });

171

172 });

173 })

174

175 // delete the user with this id

176 .delete(function(req, res) {

177 User.remove({

178 _id: req.params.user_id

179 }, function(err, user) {

180 if (err) res.send(err);

181

182 res.json({ message: 'Successfully deleted' });

183 });

184 });

185

186 return apiRouter;

187 };

If you look carefully, you’ll see this doesn’t differ too much from how our code looked in server.js. The main difference is that we are requiring config.js and using config.secret.

We are also passing back a function into module.exports and requiring that app and express are passed in. This is so we can use the express object to get an instance of express.Router();. Currently, we are not using the app object, but it note that if you are not using the express.Router(), you could directly specify routes on the app object as well.

We will then return apiRouter; so that we can use it in server.js.

Now that this file is created, we just need to call it in server.js. This is where we can see just how clean our main file has become and how much easier it is to read than before.

These are the lines to use in server.js when calling our newly created routes file:

1 // API ROUTES ------------------------

2 var apiRoutes = require('./app/routes/api')(app, express);

3 app.use('/api', apiRoutes);

If you didn’t do it earlier, go through and remove those routes and calls to packages that we moved into our api.js file. And that’s it! Now we have moved about 170 lines of code out of our server.js!

Catchall Route

The last thing we need to do is create a catchall route to pass users to an Angular application. Currently we have a route that shows our home page in server.js. It looks like this:

1 // basic route for the home page

2 app.get('/', function(req, res) {

3 res.send('Welcome to the home page!');

4 });

For creating MEAN applications, our Node routes will take place here and then any request sent to a route that isn’t handled by Node should be taken care of by Angular. This is where a catchall route comes in handy.

Any route not handled by Node will be passed to Angular. Creating the route is very easy. We are going to delete the route we created for the home page and create the following:

1 var path = require('path');

2

3 ...

4

5 var apiRoutes...

6

7 // MAIN CATCHALL ROUTE ---------------

8 // SEND USERS TO FRONTEND ------------

9 // has to be registered after API ROUTES

10 app.get('*', function(req, res) {

11 res.sendFile(path.join(__dirname + '/public/app/views/index.html'));

12 });

Make sure that path is loaded in the packages section since that is required to pass an HTML file. Using the * will match all routes. It is important to put this route after the API routes since we only want it to catch routes not handled by Node.

If this were placed above the API routes, then our user would always be sent the index.html file and never even get to the API routes.

Final Node server.js

Here’s the full server.js file:

1 // BASE SETUP

2 // ======================================

3

4 // CALL THE PACKAGES --------------------

5 var express = require('express'); // call express

6 var app = express(); // define our app using express

7 var bodyParser = require('body-parser'); // get body-parser

8 var morgan = require('morgan'); // used to see requests

9 var mongoose = require('mongoose');

10 var config = require('./config');

11 var path = require('path');

12

13 // APP CONFIGURATION ==================

14 // ====================================

15 // use body parser so we can grab information from POST requests

16 app.use(bodyParser.urlencoded({ extended: true }));

17 app.use(bodyParser.json());

18

19 // configure our app to handle CORS requests

20 app.use(function(req, res, next) {

21 res.setHeader('Access-Control-Allow-Origin', '*');

22 res.setHeader('Access-Control-Allow-Methods', 'GET, POST');

23 res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, \

24 Authorization');

25 next();

26 });

27

28 // log all requests to the console

29 app.use(morgan('dev'));

30

31 // connect to our database (hosted on modulus.io)

32 mongoose.connect(config.database);

33

34 // set static files location

35 // used for requests that our frontend will make

36 app.use(express.static(__dirname + '/public'));

37

38 // ROUTES FOR OUR API =================

39 // ====================================

40

41 // API ROUTES ------------------------

42 var apiRoutes = require('./app/routes/api')(app, express);

43 app.use('/api', apiRoutes);

44

45 // MAIN CATCHALL ROUTE ---------------

46 // SEND USERS TO FRONTEND ------------

47 // has to be registered after API ROUTES

48 app.get('*', function(req, res) {

49 res.sendFile(path.join(__dirname + '/public/app/views/index.html'));

50 });

51

52 // START THE SERVER

53 // ====================================

54 app.listen(config.port);

55 console.log('Magic happens on port ' + config.port);

Now that we have organized the Node side of things, let’s lay the groundwork for our Angular application.

Organizing AngularJS - Frontend

The only file we will create here is the index.html file since that is what we are returning with our Node catchall route. We’ll just create the folders for the rest of the application so we have the foundation ready to go.

Here’s the file structure now:

1 - app/

2 ----- models/

3 ----- routes/

4 ---------- api.js

5 - public/

6 ----- assests/

7 ---------- css/

8 --------------- style.css

9 ---------- js/

10 ---------- img/

11 ---------- libs/

12 ----- app/

13 ---------- controllers/

14 ---------- services/

15 ---------- app.routes.js

16 ---------- app.js

17 ---------- views/

18 --------------- pages/

19 --------------- index.html

20 - package.json

21 - config.js- server.js

Go ahead and create the folders and files within the public/ folder.

Here is a good starting point for our index.html file:

1 <!DOCTYPE html>

2 <html lang="en">

3 <head>

4 <meta charset="UTF-8">

5 <title>My MEAN App</title>

6 </head>

7 <body>

8

9 HELLO!

10

11 </body>

12 </html>

Testing Our Newly Organized App

Let’s make sure that everything is working properly.

Run

npm install

if you haven’t already to bring in the files that we’ll need.

Then go ahead and run:

nodemon server.js

and visit the application in your browser!

Testing App structure

Testing App structure

That was a lot of setting up. This shows you how important something as simple as directory structure is for our applications. This ensures our scalability and our ability to add on new features and components in the future. A solid infrastructure and file directory will also help other developers we work with; they will be able to understand how things are laid out.

Now let’s continue our move to a full stack MEAN application by talking about how we can link the frontend and the backend with Angular services. We will also be using services to handle frontend authentication.

After we learn about Angular services and authentication, we’ll move on to creating our full stack MEAN app.