Monday, March 30, 2020

MEAN Stack: Build an App with Angular and the Angular CLI

MEAN Stack: Build an App with Angular and the Angular CLI

For expert-led online Angular training courses, you can't go past Ultimate Angular by Todd Motto. Try his courses here, and use the code SITEPOINT to get 25% off and to help support SitePoint.

In this tutorial, we’re going to look at managing user authentication in the MEAN stack. We’ll use the most common MEAN architecture of having an Angular single-page app using a REST API built with Node, Express and MongoDB.

When thinking about user authentication, we need to tackle the following things:

  1. let a user register
  2. save user data, but never directly store passwords
  3. let a returning user log in
  4. keep a logged in user’s session alive between page visits
  5. have some pages that can only been seen by logged in users
  6. change output to the screen depending on logged in status (for example, a “login” button or a “my profile” button).

Before we dive into the code, let’s take a few minutes for a high-level look at how authentication is going to work in the MEAN stack.

The MEAN Stack Authentication Flow

So what does authentication look like in the MEAN stack?

Still keeping this at a high level, these are the components of the flow:

  • user data is stored in MongoDB, with the passwords hashed
  • CRUD functions are built in an Express API — Create (register), Read (login, get profile), Update, Delete
  • an Angular application calls the API and deals with the responses
  • the Express API generates a JSON Web Token (JWT, pronounced “Jot”) upon registration or login, and passes this to the Angular application
  • the Angular application stores the JWT in order to maintain the user’s session
  • the Angular application checks the validity of the JWT when displaying protected views
  • the Angular application passes the JWT back to Express when calling protected API routes.

JWTs are preferred over cookies for maintaining the session state in the browser. Cookies are better for maintaining state when using a server-side application.

The Example Application

The code for this tutorial is available on GitHub. To run the application, you’ll need to have Node.js installed, along with MongoDB. (For instructions on how to install, please refer to Mongo’s official documentation — Windows, Linux, macOS).

The Angular App

To keep the example in this tutorial simple, we’ll start with an Angular app with four pages:

  1. home page
  2. register page
  3. login page
  4. profile page

The pages are pretty basic and look like this to start with:

Screenshots of the app

The profile page will only be accessible to authenticated users. All the files for the Angular app are in a folder inside the Angular CLI app called /client.

We’ll use the Angular CLI for building and running the local server. If you’re unfamiliar with the Angular CLI, refer to the Building a Todo App with Angular CLI tutorial to get started.

The REST API

We’ll also start off with the skeleton of a REST API built with Node, Express and MongoDB, using Mongoose to manage the schemas. This API should initially have three routes:

  1. /api/register (POST), to handle new users registering
  2. /api/login (POST), to handle returning users logging in
  3. /api/profile/USERID (GET), to return profile details when given a USERID

Let's set that up now. We can use the express-generator tool to create a lot of the boiler plate for us. If this is new for you, we have a tutorial on using it here.

Install it with npm i -g express-generator. Then, create a new Express app, choosing Pug as the view engine:

express -v pug mean-authentication

When the generator has run, change into the project directory and install the dependencies:

cd mean-authentication
npm i

At the time of writing, this pulls in an outdated version of Pug. Let's fix that:

npm i pug@latest

We can also install Mongoose while we’re at it:

npm i mongoose

Next, we need to create our folder structure.

  • Remove the public folder: rm -rf public.
  • Create an api directory: mkdir api.
  • Create a controllers, a models, and a routes directory in the api directory: mkdir -p api/{controllers,models,routes}.
  • Create a authenication.js file and a profile.js file in the controllers directory: touch api/controllers/{authentication.js,profile.js}.
  • Create a db.js file and a users.js file in the models directory: touch api/models/{db.js,users.js}.
  • Create an index.js file in the routes directory: touch api/routes/index.js.

When you're done, things should look like this:

.
└── api
    ├── controllers
    │   ├── authentication.js
    │   └── profile.js
    ├── models
    │   ├── db.js
    │   └── users.js
    └── routes
        └── index.js

Now let's add the API functionality. Replace the code in app.js with the following:

require('./api/models/db');

const cookieParser = require('cookie-parser');
const createError = require('http-errors');
const express = require('express');
const logger = require('morgan');
const path = require('path');

const routesApi = require('./api/routes/index');

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/api', routesApi);

// catch 404 and forward to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use((err, req, res, next) => {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Add the following to api/models/db.js:

require('./users');
const mongoose = require('mongoose');
const dbURI = 'mongodb://localhost:27017/meanAuth';

mongoose.set('useCreateIndex', true);
mongoose.connect(dbURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

mongoose.connection.on('connected', () => {
  console.log(`Mongoose connected to ${dbURI}`);
});
mongoose.connection.on('error', (err) => {
  console.log(`Mongoose connection error: ${err}`);
});
mongoose.connection.on('disconnected', () => {
  console.log('Mongoose disconnected');
});

Add the following to api/routes/index.js:

const ctrlAuth = require('../controllers/authentication');
const ctrlProfile = require('../controllers/profile');

const express = require('express');
const router = express.Router();

// profile
router.get('/profile/:userid', ctrlProfile.profileRead);

// authentication
router.post('/register', ctrlAuth.register);
router.post('/login', ctrlAuth.login);

module.exports = router;

Add the following to api/controllers/profile.js:

module.exports.profileRead = (req, res) => {
  console.log(`Reading profile ID: ${req.params.userid}`);
  res.status(200);
  res.json({
    message : `Profile read: ${req.params.userid}`
  });
};

Add the following to api/controllers/authentication.js:

module.exports.register = (req, res) => {
  console.log(`Registering user: ${req.body.email}`);
  res.status(200);
  res.json({
    message : `User registered: ${req.body.email}`
  });
};

module.exports.login = (req, res) => {
  console.log(`Logging in user: ${req.body.email}`);
  res.status(200);
  res.json({
    message : `User logged in: ${req.body.email}`
  });
};

Ensure that Mongo is running and then, finally, start the server with npm run start. If everything is configured properly, you should see a message in your terminal that Mongoose is connected to mongodb://localhost:27017/meanAuth, and you should now be able to make requests to, and get responses from, the API. You can test this with a tool such as Postman.

The post MEAN Stack: Build an App with Angular and the Angular CLI appeared first on SitePoint.


by Jurgen Van de Moere via SitePoint

1 comment: