MEAN Stack 初学者教程
在本文中,我们将学习 MEAN Stack,这是一个开源 JavaScript 框架的组合。它们如何工作以及如何使用这些技术创建单页应用程序。我们不仅在前端使用这些 JavaScript 技术,还在后端使用它们。
引言
正如标题所示,本文将教您关于 MEAN Stack,MEAN 实际上是 3 个 JavaScript 框架和一种 NoSQL 或基于文档的数据库技术的组合。所以在这里,
M 代表 MongoDB
MongoDB 是一个开源的、面向文档的数据库,在设计时兼顾了可伸缩性和开发敏捷性。与关系数据库在表和行中存储数据不同,在 MongoDB 中,您存储具有动态架构的类似 JSON 的文档。
E 代表 ExpressJS
Express.js 是一个 Node.js Web 应用程序服务器框架,旨在构建单页、多页和混合 Web 应用程序。它是 Node.js 的事实标准服务器框架。
A 代表 AngularJS
AngularJS 是动态 Web 应用程序的结构化框架。它允许您使用 HTML 作为模板语言,并允许您扩展 HTML 的语法来清晰简洁地表达您的应用程序组件。Angular 的数据绑定和依赖注入消除了您通常需要编写的大部分代码。
N 代表 NodeJS
nodejs.org。Node.js 是一个开源的、跨平台的运行时环境,用于开发服务器端 Web 应用程序。Node.js 应用程序用 JavaScript 编写,并且可以在 OS X、Microsoft Windows、Linux、FreeBSD、NonStop、IBM AIX、IBM System z 和 IBM 上的 Node.js 运行时中运行。
实际上,MEAN 是
MEAN Stack 这个术语指的是用于开发 Web 应用程序的 JavaScript 技术集合。MEAN 是 MongoDB、ExpressJS、AngularJS 和 Node.js 的首字母缩写。从客户端到服务器再到数据库,MEAN 是全栈 JavaScript。
让我告诉您它们是如何协同工作的,Angular 实际上用于前端开发。我将使用 Angular.js 设计视图,以便在单页上呈现视图。对于服务器端,我将使用 Node.js,它实际上内部使用 Express.js。通过 Express,我将编写一个 API 来与数据库通信。最后,我将使用 MongoDb 存储数据。如图所示
那么让我们从头开始。
工具
在继续之前,您必须安装 Node 和 MongoDB。您可以在此处找到它们
在本教程中,我将使用最新发布的 Visual Studio Code 0.9.2,但您也可以使用 Sublime Text 编辑器。我将通过 Node Package Manager 使用 Windows CMD 提示符安装包。安装 Node.js 后,您可以使用 node cmd 非常轻松地安装它。
背景
这里的基本思想是开始学习 MEAN Stack 和 Node 的工作原理,Stack Overflow 上很多基本问题促使我写了这篇文章。因此,本教程向您展示了如何使用相应的控制器和模块基本注册用户和认证用户。
使用代码
我有一个名为 MeanApp 的主目录。这个根目录包含不同的子目录,我将描述一些主目录。
1) Angular 将包含三个子目录,Controllers 用于 angular 控制器,Modules 和 Models。
2) Public 将包含所有 javascript 库。
3) Routes 将包含 expressjs api,它们将处理请求并与 MongoDB 交互。
4) Views 将包含其各自文件夹中的所有相应视图。
在根文件夹上,我使用 server.js 作为我的启动文件。所以让我们开始吧。
要将其他开源库安装到您的应用程序中,您只需要通过 npm 包管理器安装它们。使用命令 npm install [package] --save
您需要运行以下命令来安装所需的教程包
npm install express --save
npm install path --save
npm install morgan --save
npm install cookie-parser --save
npm install body-parser --save
npm install bcrypt-nodejs --save
npm install passport --save
npm install passport-local --save
npm install express-session --save
npm install mongoose --save
让我们从设计用户界面开始,首先在 Angular/Modules 文件夹中创建一个新的 modules.js 文件。
//Angular Starter App
var main = angular.module("main", ['ui.router','ngRoute','ngResource'])
.run(function($http,$rootScope)
{
if(sessionStorage.length > 0){
$rootScope.current_user = sessionStorage.current_user;
$rootScope.authenticated = true;
}else{
$rootScope.authenticated = false;
$rootScope.current_user = 'Guest';
}
$rootScope.signout = function(){
$http.get('auth/signout');
$rootScope.authenticated = false;
$rootScope.current_user = 'Guest';
sessionStorage.clear();
};
});
//Routing Configuration (define routes)
main.config([
'$stateProvider', '$urlRouterProvider', '$httpProvider',
function ($stateProvider, $urlRouterProvider,$rootScope) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateUrl: 'Index.html',
caseInsensitiveMatch: true,
controller: 'MainController'
})
.state('contact', {
url: '/contact',
templateUrl: 'Contact.html',
caseInsensitiveMatch: true,
controller: 'MainController'
})
.state('about', {
url: '/about',
templateUrl: 'About.html',
caseInsensitiveMatch: true,
controller: 'MainController'
})
.state('login',{
url: '/login',
templateUrl: 'login.html',
caseInsensitiveMatch: true,
controller: 'AuthController'
})
.state('register',{
url: '/register',
templateUrl: 'register.html',
caseInsensitiveMatch: true,
controller: 'AuthController'
}).state('unauth',{
url: '/unauth',
templateUrl: 'unauth.html',
caseInsensitiveMatch: true
});
}
]);
现在让我们在 Angular/Models 文件夹中创建一个 Model user.js
//create new model
var mongoose = require('mongoose'); //refering mongoose for creating user friendly class type model.
//defining schema for user model
var userSchema = new mongoose.Schema({
username: String,
password: String,
email: String,
role: String,
created_at: {type: Date, default: Date.now}
});
mongoose.model('User', userSchema);
var User = mongoose.model('User');
exports.findByUsername = function(userName, callback){
User.findOne({ user_name: userName}, function(err, user){
if(err){
return callback(err);
}
return callback(null, user);
});
}
exports.findById = function(id, callback){
User.findById(id, function(err, user){
if(err){
return callback(err);
}
return callback(null, user);
});
}
我们已经完成了 Model,现在在 Angular/Controller 文件夹中创建两个新的 Controllers AuthController.js 和 MainController.js。
//auth controller
main.controller("AuthController", function ($scope, $http, $rootScope, $location) {
$scope.user = {username: '', password: ''};
$scope.error_message = '';
//login call to webapi (node implemented service)
$scope.login = function(){
$http.post('/auth/login', $scope.user).success(function(data){
if(data.state == 'success'){
$rootScope.authenticated = true;
$rootScope.current_user = data.user.username;
$rootScope.sess = data.user;
sessionStorage.setItem('current_user', $rootScope.sess.username);
$location.path('/');
}
else{
$scope.error_message = data.message;
$rootScope.sess = null;
}
});
};
//login call to webapi (node implemented service)
$scope.register = function(){
console.log($scope.user);
$http.post('/auth/signup', $scope.user).success(function(data){
if(data.state == 'success'){
$rootScope.authenticated = true;
$rootScope.current_user = data.user.username;
$location.path('/');
}
else{
$scope.error_message = data.message;
}
});
};
});
//for main controller you can define any thing to make one way binding in main view.
main.controller("MainController", function ($scope) {}); //left empty, if not necessary don't create.
让我们创建将要渲染的视图,创建新文件夹 Views,并在主目录中将以下 ejs 代码添加为 ejs 是 nodejs 在 Starter.ejs 文件中使用的渲染引擎。
//<!DOCTYPE html>
<html ng-app="main">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width" />
<title>Super Application</title>
<link href="bootstrap.css" rel="stylesheet" />
<link href="Site.css" rel="stylesheet" />
<script src="modernizr-2.6.2.js"></script>
<script src="jquery-1.10.2.js"></script>
<script src="bootstrap.js"></script>
<script src="angular.js"></script>
<script src="angular-route.js"></script>
<script src="angular-ui-router.js"></script>
<script src="angular-resource.js"></script>
<script src="/Modules/mainApp.js"></script>
<script src="/Controllers/MainController.js"></script>
<script src="/Controllers/AuthController.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#/home">Application name</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="#/home">Home</a></li>
<li><a href="#/about">About</a></li>
<li><a href="#/contact">Contact</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><p class="navbar-right navbar-text">Signed in as {{current_user}}</p></li>
<li><p class="navbar-right navbar-text" ng-hide="authenticated">
<a href="#/login">Login</a> or <a href="#/register">Register</a>
</p></li>
<li><p class="navbar-right navbar-text" ng-show="authenticated">
<a href="#" ng-click="signout()">Logout</a>
</p></li>
</ul>
</div>
</div>
</div>
<div class="container body-content">
<div ui-view>
</div>
<hr />
<footer>
<p>My ASP.NET Application</p>
</footer>
</div>
</body>
</html>
现在在 Views 文件夹内创建新文件夹 Authentication,此文件夹将包含所有身份验证视图(注册、登录等)。
对于登录视图,在 Authentication 文件夹中添加一个名为 login.html 的新文件。
<form class="form-auth" ng-submit="login()">
<h2>Log In</h2>
<p class="text-warning">{{error_message}}</p>
<input type="username" ng-model="user.username" placeholder="Username" class="form-control" required><br>
<input type="password" ng-model="user.password" placeholder="Password" class="form-control" required><br>
<input type="submit" value="Log in" class="btn btn-primary" />
</form>
对于注册视图,在 Authentication 文件夹中添加一个名为 register.html 的新文件。
<form class="form-auth" ng-submit="register()">
<h2>Register</h2>
<p class="text-warning">{{error_message}}</p>
<input type="email" ng-model="user.email" placeholder="Email" class="form-control" required><br>
<input type="username" ng-model="user.username" placeholder="Username" class="form-control" required><br>
<input type="password" ng-model="user.password" placeholder="Password" class="form-control" required><br>
<select ng-init="user.role = roles[0].name" ng-model="user.role" ng-options="role.name as role.name for role in roles" class="form-control" required></select><br>
<input type="submit" value="Sign Up" class="btn btn-primary" />
</form>
对于 unauth 视图,在 Authentication 文件夹中添加一个名为 unauth.html 的新文件。
<form class="form-auth">
<h2>You are Authentic/Unauthorize to access this page, This is because </h2>
<p>1) Not login? Please register to access resources.</p>
<p>2) Registered: You are not Authorize user, Please contact Administrator.</p>
</form>
现在在 Views 文件夹内创建新文件夹 Main ,此文件夹将包含所有主视图(index、aboutus、contact 等)。
对于 Index 视图,在 Main 文件夹中添加一个名为 index.html 的新文件。
<div>
<div class="jumbotron">
<h1>Node.js Application</h1>
<p class="lead">Node.js is a free Javascript framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a class="btn btn-primary btn-lg">Learn more »</a></p>
</div>
<div class="row">
<div class="col-md-4">
<h2>Getting started</h2>
<p>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
enables a clean separation of concerns and gives you full control over markup
for enjoyable, agile development.
</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Get more libraries</h2>
<p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301866">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Web Hosting</h2>
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
</div>
</div>
</div>
对于 Aboutus 视图,在 Main 文件夹中添加一个名为 About.html 的新文件。
<div>
<h2>About Us</h2>
<h3>Message</h3>
<p>Use this area to provide additional information.</p>
</div>
对于 Index 视图,在 Main 文件夹中添加一个名为 Contact.html 的新文件。
<div>
<h2>Contact Us</h2>
<h3>Message</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
</div>
我们现在已经完成了 Angular 设计方面的工作。
让我们用 node.js 创建您的后端
在 Routes 文件夹中从身份验证请求创建新路由,名称为 authentication.js。
var express = require('express');
var router = express.Router();
module.exports = function(passport){
//sends successful login state back to view(angular)
router.get('/success',function(req,res){
res.send({state: 'success', user: req.user ? req.user: null});
});
//send failure login state back to view(angular)
router.get('/failure',function(req,res){
res.send({state: 'failure',user:null,message:"Invalid username or password"});
});
//login requeset
router.post('/login',passport.authenticate('login',{
successRedirect: '/auth/success',
failureRedirect: '/auth/failure'
}));
//signup request
router.post('/signup', passport.authenticate('signup', {
successRedirect: '/auth/success',
failureRedirect: '/auth/failure'
}));
//logout request
router.get('/signout', function(req, res) {
req.session.user = null;
req.logout();
res.redirect('/');
});
return router;
}
在同一个文件夹中创建 router.js。
var express = require('express');
var router = express.Router();
var mongoose = require( 'mongoose' );
router.get('/',function(req,res,next){
res.render('Starter',{title:"Super App"});
});
module.exports = router;
创建名为 Passport 的新文件夹,并添加一个新文件(API)名称为 passport-init.js 并添加以下代码。您的身份验证路由将调用此身份验证 API。
var mongoose = require('mongoose');
var User = mongoose.model('User');
var LocalStrategy = require('passport-local').Strategy;
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport){
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function(user, done) {
console.log('serializing user:',user.username);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
console.log('deserializing user:',user.username);
done(err, user);
});
});
passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({ 'username' : username },
function(err, user) {
// In case of any error, return using the done method
if (err)
return done(err);
// Username does not exist, log the error and redirect back
if (!user){
console.log('User Not Found with username '+username);
return done(null, false);
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}
);
}
));
passport.use('signup', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done, email, role) {
// find a user in mongo with provided username
User.findOne({ 'username' : username }, function(err, user) {
// In case of any error, return using the done method
if (err){
console.log('Error in SignUp: '+ err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with username: '+username);
return done(null, false);
} else {
// if there is no user, create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
newUser.email = req.body.email;
newUser.role = req.body.role;
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
throw err;
}
console.log(newUser.username + ' Registration succesful');
return done(null, newUser);
});
}
});
})
);
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
};
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
};
};
现在,在根目录中添加一个新文件 server.js,因为这将是我们的主启动文件。
//server.js
//adding opensource modules to application
var express = require('express'); //express
var path = require('path'); //for refering physical files here
var logger = require('morgan');
var cookieParser = require('cookie-parser'); //for maintain sessions
var bodyParser = require('body-parser'); //for parsing json
var bcrypt = require('bcrypt-nodejs');
var passport = require('passport'); //Using passportjs for authentication
var LocalStrategy = require('passport-local').Strategy; //using passport strategy
var session = require('express-session'); //for maintaining sessions
var mongoose = require('mongoose'); //for mongodb, database
var models_user = require('./Angular/Models/user.js'); // refering models in server.js
//connection database
mongoose.connect('mongodb:///AngularizeApp');
//import the routers
var router = require('./Routes/router');
var authenticate = require('./Routes/authentication')(passport);
//for using express throughout this application
var app = express();
//tell node that My application will use ejs engine for rendering, view engine setup
app.set('views', path.join(__dirname, 'Views'));
app.set('view engine', 'ejs');
//tell node the global configuration about parser,logger and passport
app.use(cookieParser());
app.use(logger('dev'));
app.use(session({
secret: 'keyboard cat'
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize()); //initializing passport
app.use(passport.session()); //initializing passport session
//tell node about these directories that application may get resources from
app.use('/', router);
app.use('/auth', authenticate);
app.use(express.static(path.join(__dirname, 'scripts')));
app.use(express.static(path.join(__dirname, 'Content')));
app.use(express.static(path.join(__dirname, 'Angular')));
app.use(express.static(path.join(__dirname, 'Views/Main')));
app.use(express.static(path.join(__dirname, 'Views/Authentication')));
//providing auth-api to passport so that it can use it.
var initPassport = require('./Passport/passport-init');
initPassport(passport);
//running server on node
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
//exporting this application as a module
module.exports = app;
现在我们的代码已经完成,您可以通过在 cmd 中运行 node server.js 命令来运行此应用程序,首先使用 cd c://MeanApp 等命令将其指向 MEANApp 根文件夹。
您还需要运行 mongodb 服务器,为此打开一个 cmd 新实例并键入 mongod -dbpath . 如果 mongodb 已安装并添加了变量名,此命令将成功运行您的 mongodb 服务器。保持该 cmd 打开,因为如果您关闭该 cmd 实例,服务器也将关闭。
mongod -dbpath .
运行上述命令后,您现在可以在 cmd 中查看数据库,为此打开另一个 cmd 实例并键入 mongodb,您将看到客户端已连接,您可以切换到所需的数据库。您可以在此处提供的文档中了解有关 mongodb 的信息。此处。
您可以通过以下命令运行应用程序
MeanApp>node server.js
登录后,您可以相应地检查调用。
现在我们将能够看到正在运行的带有身份验证的应用程序,并包含用于在服务器端存储数据的会话。
以下教程具有 Yeomen Scaffolding 提供的所有功能,但是本教程主要是为了通过理解来学习事物是如何完成的。**
您可以在此处从 github 获取代码。
您也可以在此处下载代码。