├── .angular-cli.json ├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .vscode └── launch.json ├── Dockerfile ├── README.md ├── TUTORIAL.md ├── TUTORIAL_BREAKDOWN.md ├── VIDEOS.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── client │ ├── app │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── hero.service.ts │ │ ├── hero.ts │ │ ├── heroes.component.html │ │ └── heroes.component.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts └── server │ ├── env │ └── example-environment.js │ ├── hero.model.js │ ├── hero.service.js │ ├── index.js │ ├── mongo.js │ ├── package.json │ └── routes.js ├── tsconfig.json └── tslint.json /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "angular-cosmosdb" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src/client", 9 | "outDir": "dist/publicweb", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico", 13 | { 14 | "glob": "**/*.*", 15 | "input": "../server/", 16 | "output": "../" 17 | } 18 | ], 19 | "index": "index.html", 20 | "main": "main.ts", 21 | "polyfills": "polyfills.ts", 22 | "test": "test.ts", 23 | "tsconfig": "tsconfig.app.json", 24 | "testTsconfig": "tsconfig.spec.json", 25 | "prefix": "app", 26 | "styles": ["styles.scss"], 27 | "scripts": [], 28 | "environmentSource": "environments/environment.ts", 29 | "environments": { 30 | "dev": "environments/environment.ts", 31 | "prod": "environments/environment.prod.ts" 32 | } 33 | } 34 | ], 35 | "e2e": { 36 | "protractor": { 37 | "config": "./protractor.conf.js" 38 | } 39 | }, 40 | "lint": [ 41 | { 42 | "project": "src/client/tsconfig.app.json", 43 | "exclude": "**/node_modules/**" 44 | }, 45 | { 46 | "project": "src/client/tsconfig.spec.json", 47 | "exclude": "**/node_modules/**" 48 | }, 49 | { 50 | "project": "e2e/tsconfig.e2e.json", 51 | "exclude": "**/node_modules/**" 52 | } 53 | ], 54 | "test": { 55 | "karma": { 56 | "config": "./karma.conf.js" 57 | } 58 | }, 59 | "defaults": { 60 | "styleExt": "scss", 61 | "component": { 62 | "spec": false, 63 | "inlineStyle": true, 64 | "inlineTemplate": true 65 | }, 66 | "directive": { 67 | "spec": false 68 | }, 69 | "class": { 70 | "spec": false 71 | }, 72 | "guard": { 73 | "spec": false 74 | }, 75 | "module": { 76 | "spec": false 77 | }, 78 | "pipe": { 79 | "spec": false 80 | }, 81 | "service": { 82 | "spec": false 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | README.md 9 | LICENSE 10 | .vscode 11 | 12 | dist/node_modules 13 | dist/npm-debug.log 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "arrow-body-style": ["warn", "as-needed"], 16 | "arrow-parens": ["warn", "as-needed"], 17 | "arrow-spacing": ["warn", { "before": true, "after": true }], 18 | "max-len": ["error", 100], 19 | "prefer-arrow-callback": [ 20 | "warn", 21 | { 22 | "allowNamedFunctions": true 23 | } 24 | ], 25 | "quotes": ["error", "single"], 26 | "no-var": "warn", 27 | "prefer-const": "warn", 28 | "no-const-assign": "warn", 29 | "no-this-before-super": "warn", 30 | "no-undef": "warn", 31 | "no-unreachable": "warn", 32 | "no-unused-vars": "warn", 33 | "constructor-super": "warn", 34 | "valid-typeof": "warn" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # ignore server configuration 4 | src/server/env/environment.js 5 | 6 | # compiled output 7 | /dist 8 | /tmp 9 | /out-tsc 10 | 11 | # dependencies 12 | /node_modules 13 | 14 | # IDEs and editors 15 | /.idea 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # IDE - VSCode 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | 30 | # misc 31 | /.sass-cache 32 | /connect.lock 33 | /coverage 34 | /libpeerconnection.log 35 | npm-debug.log 36 | testem.log 37 | /typings 38 | 39 | # e2e 40 | /e2e/*.js 41 | /e2e/*.map 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Launch Program", 12 | "program": "${workspaceRoot}/src/server/index.js" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Angular App ======================================== 2 | FROM johnpapa/angular-cli as angular-app 3 | LABEL authors="Shayne Boyer, John Papa" 4 | # Copy and install the Angular app 5 | WORKDIR /app 6 | COPY package.json /app 7 | RUN npm install 8 | COPY . /app 9 | RUN ng build --prod 10 | 11 | #Express server ======================================= 12 | FROM node:6.11-alpine as express-server 13 | WORKDIR /app 14 | COPY /src/server /app 15 | RUN npm install --production --silent 16 | 17 | #Final image ======================================== 18 | FROM node:6.11-alpine 19 | RUN mkdir -p /usr/src/app 20 | WORKDIR /usr/src/app 21 | COPY --from=express-server /app /usr/src/app 22 | COPY --from=angular-app /app/dist /usr/src/app 23 | ENV PORT 80 24 | #ENV API_URL we-could-set-this-here-as-default 25 | CMD [ "node", "index.js" ] 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Cosmos DB 2 | 3 | by [John Papa](http://twitter.com/john_papa) 4 | 5 | You can [watch me build the app as part of my series here](https://johnpapa.net/angular-cosmosdb-1/) 6 | 7 | You can [view all videos together here](/VIDEOS.md) 8 | 9 | [Learn more about developing Node.js apps with Azure's cloud services here](https://docs.microsoft.com/nodejs/azure?WT.mc_id=angularcosmosdb-github-jopapa) 10 | 11 | ## Docker 12 | 13 | Create the Docker image and run it locally 14 | 15 | ```bash 16 | dockerImage=angular-cosmosdb 17 | docker build -t $dockerImage . 18 | docker run -d -p 3000:80 $dockerImage 19 | ``` 20 | 21 | ## Requirements 22 | 23 | 1. Install the Angular CLI 24 | 25 | ```bash 26 | npm install -g @angular/cli 27 | ``` 28 | 29 | 1. Install the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?WT.mc_id=angularcosmosdb-github-jopapa) 30 | 31 | ## Getting Started 32 | 33 | 1. Clone this repository 34 | 35 | ```bash 36 | git clone https://github.com/johnpapa/angular-cosmosdb.git 37 | cd angular-cosmosdb 38 | ``` 39 | 40 | 1. Install the npm packages 41 | 42 | ```bash 43 | npm i 44 | ``` 45 | 46 | 1. Configure Cosmos DB server settings 47 | 48 | Rename the `example-environment.js` file to `environment.js` in the `server/env/` folder and update it with your Cosmos DB settings. Replace the database name key, and port with your specific configuration. 49 | 50 | ```javascript 51 | // server/env/environment.js 52 | const cosmosPort = 1234; // replace with your port 53 | const dbName = 'your-cosmos-db-name-goes-here'; 54 | const key = 'your-key-goes-here'; 55 | 56 | module.exports = { 57 | cosmosPort, 58 | dbName, 59 | key 60 | }; 61 | ``` 62 | 63 | ## Running the app 64 | 65 | 1. Build the Angular app 66 | 67 | ```bash 68 | ng build 69 | ``` 70 | 71 | 1. Launch the server 72 | 73 | ```bash 74 | node src/server/index.js 75 | ``` 76 | 77 | 1. Open the browser to http://localhost:3000 78 | 79 | ## Problems or Suggestions 80 | 81 | [Open an issue here](https://github.com/johnpapa/angular-cosmos/issues) 82 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # MEAN app with Cosmos DB 2 | 3 | ## Angular app 4 | 5 | 1. Install the Angular CLI 6 | 7 | ```bash 8 | npm install @angular/cli -g 9 | ``` 10 | 11 | 1. Generate a new Angular app with routing. We indicate we want the Angular app to be placed in the `src/client` folder, `--minimal` indicates we should create inline styles and templates, and we don't want test files. `--style scss` indicates we will use SASS instead of CSS. 12 | 13 | ```bash 14 | ng new angular-cosmosdb -sd src/client --minimal --style scss 15 | cd angular-cosmosdb 16 | ``` 17 | 18 | ## Node and Express app 19 | 20 | 1. Install Express, and body parser. 21 | 22 | ```bash 23 | npm install express body-parser --save 24 | ``` 25 | 26 | 1. Open VS Code 27 | 28 | 1. Create a new folder and file for `src/server/index.js` 29 | 30 | 1. Type this code into `index.js` 31 | 32 | ```javascript 33 | const express = require('express'); 34 | const path = require('path'); 35 | const bodyParser = require('body-parser'); 36 | //const routes = require('./routes'); 37 | 38 | const root = './'; 39 | const port = process.env.PORT || '3000'; 40 | const app = express(); 41 | 42 | app.use(bodyParser.json()); 43 | app.use(bodyParser.urlencoded({ extended: false })); 44 | app.use(express.static(path.join(root, 'dist'))); 45 | //app.use('/api', routes); 46 | app.get('*', (req, res) => { 47 | res.sendFile('dist/index.html', {root: root}); 48 | }); 49 | 50 | app.listen(port, () => console.log(`API running on localhost:${port}`)); 51 | ``` 52 | 53 | 1. Create `src/server/routes.js` 54 | 55 | 1. Create a `/api` route (link to routes.js) 56 | 57 | 1. Create a `/api/get` route 58 | 59 | ```javascript 60 | const express = require('express'); 61 | const router = express.Router(); 62 | 63 | // const heroService = require('./hero.service'); 64 | 65 | router.get('/heroes', (req, res) => { 66 | //heroService.getHeroes(req, res); 67 | res.send(200, [ 68 | {"id": 10,"name": "Starlord","saying": "oh yeah"} 69 | ]); 70 | }); 71 | 72 | module.exports = router; 73 | ``` 74 | 75 | 1. Create a VS Code debug configuration for Node, and press the green arrow to run it! (Press debug, press configure, select Node.js, press green button) 76 | 77 | 1. Change the launch config's launch program to our node server 78 | 79 | ```json 80 | "program": "${workspaceRoot}/src/server/index.js" 81 | ``` 82 | 83 | 1. Open Postman and paste perform a *GET* http request on http://localhost:3000/api/heroes. You should see the following JSON results 84 | 85 | ## Adding the A in MEAN 86 | 87 | ### The Heroes UI 88 | 89 | 1. Add a component to list Heroes. The `--flat` flag adds the component without putting it in its own sub folder. 90 | 91 | ```bash 92 | ng g c heroes --flat 93 | ``` 94 | 95 | 1. Copy this code into `heroes.component.html` 96 | 97 | 1. Copy the styles.scss code 98 | 99 | 1. Copy this code into `heroes.component.ts` 100 | 101 | 1. Add the hero component's selector to the `app.component.ts` 102 | 103 | ```javascript 104 | import { Component } from '@angular/core'; 105 | 106 | @Component({ 107 | selector: 'app-root', 108 | template: ` 109 |

Heroes

110 | 111 | ` 112 | }) 113 | export class AppComponent {} 114 | ``` 115 | 116 | 1. Import Forms and Http modules into the app 117 | 118 | ```javascript 119 | import { FormsModule } from '@angular/forms'; 120 | import { HttpClientModule } from '@angular/common/http'; 121 | 122 | // ... 123 | 124 | imports: [BrowserModule, FormsModule, HttpClientModule], 125 | ``` 126 | 127 | 1. Create a model for our heroes, `hero.ts` 128 | 129 | ```bash 130 | ng g cl hero 131 | ``` 132 | 133 | 1. Add a service to handle http interactions between Angular and Node 134 | 135 | ```bash 136 | ng g s hero -m app.module 137 | ``` 138 | 139 | 1. Copy the code into `hero.service.ts` 140 | 1. Build the app 141 | 142 | ```bash 143 | ng build 144 | ``` 145 | 146 | 1. Press the green arrow to run it! 147 | 1. Open the browser to http://localhost:3000 148 | 149 | ## Create a Cosmos DB account 150 | 151 | ### Cosmos DB setup 152 | 153 | 1. Install the Azure CLI 154 | 155 | 1. Login to Azure via the CLI 156 | 157 | ```bash 158 | # interactive login to Azure 159 | az login 160 | ``` 161 | 162 | 1. Create a logical place for our Azure resources. This is a called a resource group. 163 | 164 | ```bash 165 | # Create a resource group (logical container for our Azure resources) 166 | az group create -n my-heroes-group -l "East US" 167 | ``` 168 | 169 | 1. Create the Cosmos DB account and make sure it is of type MongoDB 170 | 171 | ```bash 172 | # Create our Cosmos DB 173 | az cosmosdb create -n my-heroes-db -g my-heroes-group --kind MongoDB 174 | ``` 175 | 176 | ### Connecting to Mongo 177 | 178 | 1. Install the mongoose node package from npm 179 | 180 | ```bash 181 | npm install mongoose --save 182 | ``` 183 | 184 | 1. Create `src/server/mongo.js` to handle mongo connections 185 | 186 | ```javascript 187 | const mongoose = require('mongoose'); 188 | /** 189 | * Set to Node.js native promises 190 | * Per http://mongoosejs.com/docs/promises.html 191 | */ 192 | mongoose.Promise = global.Promise; 193 | 194 | const env = require('./env/environment'); 195 | 196 | // eslint-disable-next-line max-len 197 | const mongoUri = `mongodb://${env.dbName}:${env.key}@${env.dbName}.documents.azure.com:${env.cosmosPort}/?ssl=true`; 198 | 199 | function connect() { 200 | return mongoose.connect(mongoUri, { useMongoClient: true }); 201 | } 202 | 203 | module.exports = { 204 | connect, 205 | mongoose 206 | }; 207 | 208 | ``` 209 | 210 | 1. Create the connection environment file `src/server/env/env.js` 211 | 212 | ```javascript 213 | const cosmosPort = 1234; // replace with your port 214 | const dbName = 'your-cosmos-db-name-goes-here'; 215 | const key = 'your-key-goes-here'; 216 | 217 | module.exports = { 218 | dbName, 219 | key, 220 | cosmosPort 221 | }; 222 | ``` 223 | 224 | 1. Enter the following Azure CLI commands to get the password and connection string. Copy these to your text editor 225 | 226 | ```bash 227 | # Get the key for the Cosmos DB 228 | az cosmosdb list-keys -n my-heroes-db -g my-heroes-group --query "primaryMasterKey" 229 | 230 | # Get the connection string 231 | az cosmosdb list-connection-strings -n my-cosmos-heroes -g my-heroes-db-group --query "connectionStrings[0].connectionString" 232 | ``` 233 | 234 | 1. Replace your port, database name, and password/key for Cosmos DB. You can find these in the Azure portal for your Cosmos DB account. 235 | 236 | 1. Create the Hero model `src/server/hero.model.js` 237 | 238 | 1. Enter the following code to create the Hero model using a mongoose schema 239 | 240 | ```javascript 241 | const mongoose = require('mongoose'); 242 | const Schema = mongoose.Schema; 243 | const heroSchema = new Schema( 244 | { 245 | id: { type: Number, required: true, unique: true }, 246 | name: String, 247 | saying: String 248 | }, 249 | { 250 | collection: 'heroes', 251 | read: 'nearest' 252 | } 253 | ); 254 | const Hero = mongoose.model('Hero', heroSchema); 255 | module.exports = Hero; 256 | ``` 257 | 258 | ### Create a service to handle heroes data using Mongo via Mongoose 259 | 260 | 1. Create `src/server/hero.service.js` 261 | 262 | ```javascript 263 | const Hero = require('./hero.model'); 264 | 265 | require('./mongo').connect(); 266 | 267 | function getHeroes(req, res) { 268 | const docquery = Hero.find({}); 269 | docquery 270 | .exec() 271 | .then(heroes => { 272 | res.status(200).json(heroes); 273 | }) 274 | .catch(error => { 275 | res.status(500).send(error); 276 | return; 277 | }); 278 | } 279 | 280 | module.exports = { 281 | getHeroes 282 | }; 283 | ``` 284 | 285 | 1. Modify `src/server/routes.js` to use the new `hero.service.js` 286 | 287 | ```javascript 288 | const express = require('express'); 289 | const router = express.Router(); 290 | 291 | const heroService = require('./hero.service'); 292 | 293 | router.get('/heroes', (req, res) => { 294 | heroService.getHeroes(req, res); 295 | //res.send(200, [ 296 | // {"id": 10,"name": "Starlord","saying": "oh yeah"} 297 | //]); 298 | }); 299 | 300 | module.exports = router; 301 | ``` 302 | 303 | 1. Restart the node process in the VS Code debugger. 304 | 305 | 1. Open Postman and paste perform a *GET* http request on http://localhost:3000/api/heroes. You should see the following JSON results 306 | 307 | 1. Open the app at http://localhost:3000. You should see the app no longer has any heroes, because we are hitting the new database. 308 | 309 | ## Create the post, put and delete methods in the node server 310 | 311 | ### Create the Hero Post 312 | 313 | 1. In the `src/server/hero.service`, create the post method and the helper functions. 314 | 315 | ```javascript 316 | function postHero(req, res) { 317 | const originalHero = { id: req.body.id, name: req.body.name, saying: req.body.saying }; 318 | const hero = new Hero(originalHero); 319 | hero.save(error => { 320 | if (checkServerError(res, error)) return; 321 | res.status(201).json(hero); 322 | console.log('Hero created successfully!'); 323 | }); 324 | } 325 | 326 | function checkServerError(res, error) { 327 | if (error) { 328 | res.status(500).send(error); 329 | return error; 330 | } 331 | } 332 | 333 | module.exports = { 334 | getHeroes, 335 | postHero 336 | }; 337 | ``` 338 | 339 | 1. In the `src/server/routes.js`, call the post function 340 | 341 | ```javascript 342 | router.post('/hero', (req, res) => { 343 | heroService.postHero(req, res); 344 | }); 345 | ``` 346 | 347 | 1. Restart the node process in the VS Code debugger. 348 | 349 | 1. Open the app at http://localhost:3000. Add a hero 350 | 351 | ### Create the PUT and DELETE 352 | 353 | 1. In the `src/server/hero.service`, create the post method and the helper functions. 354 | 355 | ```javascript 356 | function putHero(req, res) { 357 | const id = parseInt(req.params.id, 10); 358 | const updatedHero = { 359 | id: id, 360 | name: req.body.name, 361 | saying: req.body.saying 362 | }; 363 | Hero.findOne({ id: id }, (error, hero) => { 364 | if (checkServerError(res, error)) return; 365 | if (!checkFound(res, hero)) return; 366 | 367 | hero.name = updatedHero.name; 368 | hero.saying = updatedHero.saying; 369 | hero.save(error => { 370 | if (checkServerError(res, error)) return; 371 | res.status(200).json(hero); 372 | console.log('Hero udpated successfully!'); 373 | }); 374 | }); 375 | } 376 | 377 | function deleteHero(req, res) { 378 | const id = parseInt(req.params.id, 10); 379 | Hero.findOneAndRemove({ id: id }) 380 | .then(hero => { 381 | if (!checkFound(res, hero)) return; 382 | res.status(200).json(hero); 383 | console.log('Hero deleted successfully!'); 384 | }) 385 | .catch(error => { 386 | if (checkServerError(res, error)) return; 387 | }); 388 | } 389 | 390 | function checkFound(res, hero) { 391 | if (!hero) { 392 | res.status(404).send('Hero not found.'); 393 | return; 394 | } 395 | return hero; 396 | } 397 | 398 | module.exports = { 399 | getHeroes, 400 | postHero, 401 | putHero, 402 | deleteHero 403 | }; 404 | ``` 405 | 406 | 1. In the `src/server/routes.js`, call the post function 407 | 408 | ```javascript 409 | router.post('/hero', (req, res) => { 410 | heroService.postHero(req, res); 411 | }); 412 | ``` 413 | 414 | 1. Restart the node process in the VS Code debugger. 415 | 416 | 1. Open the app at http://localhost:3000. Get, Add, Update, Delete ! 417 | -------------------------------------------------------------------------------- /TUTORIAL_BREAKDOWN.md: -------------------------------------------------------------------------------- 1 | # MEAN app with Cosmos DB 2 | 3 | ## Create and run the API 4 | 5 | We'll create the Angular app and add the Node and Express code to handle API routing. 6 | 7 | ### Create the app 8 | 9 | 1. Install the Angular CLI 10 | 1. Generate a new Angular app 11 | 12 | ### Node and Express 13 | 14 | 1. Install Express, and body parser. 15 | 1. Create a new folder and file for `src/server/index.js`, add basic node/express logic 16 | 1. Create `src/server/routes.js` copy the routes in 17 | 1. Create a `/api/get` route (with hard coded heroes) 18 | 1. Create a `/api` route in `src/server/index.js` (link to routes.js) 19 | 1. Create a VS Code debug configuration for Node (Press debug, press configure, select Node.js, press green button) 20 | 1. Change the launch config's launch program to our node server 21 | 1. Press the green arrow to run the node server 22 | 1. Open Postman and paste perform a *GET* http request on http://localhost:3000/api/heroes. You should see the following JSON results 23 | 24 | ## Adding the A in MEAN 25 | 26 | ### The Heroes UI 27 | 28 | 1. Add a component to list Heroes. The `--flat` flag adds the component without putting it in its own sub folder. 29 | 1. Copy this code into `heroes.component.html` 30 | 1. Copy this code into `heroes.component.ts` 31 | 1. Copy the styles.scss code 32 | 1. Add the hero component's selector to the `app.component.ts` 33 | 1. Import Forms and Http modules into the app 34 | 1. Create a model for our heroes, `hero.ts` 35 | 1. Add a service to handle http interactions between Angular and Node 36 | 1. Copy the code into `hero.service.ts` 37 | 1. Build the app 38 | 1. Create a VS Code debug configuration for Node, and press the green arrow to run it! 39 | 1. Open the browser to http://localhost:3000 40 | 41 | ## Cosmos DB setup 42 | 43 | 1. Install the Azure CLI 44 | 1. Login to Azure via the CLI 45 | 1. Create a logical place for our Azure resources. This is a called a resource group. 46 | 1. Create the Cosmos DB account and make sure it is of type MongoDB 47 | 1. Check it out in the Portal 48 | 49 | ### Connecting to Mongo 50 | 51 | 1. Install the mongoose node package from npm 52 | 1. Create `src/server/mongo.js` to handle mongo connections 53 | 1. Create the connection environment file `src/server/env/environment.js` 54 | 1. Enter the following Azure CLI commands to get the password and connection string. Copy these to your text editor, as we'll need them in a moment. 55 | 1. Replace your port, database name, and password/key for Cosmos DB. You can find these in the Azure portal for your Cosmos DB account. 56 | 1. Create the Hero model `src/server/hero.model.js` 57 | 1. Enter the following code to create the Hero model using a mongoose schema 58 | 1. Create a service to handle heroes data using Mongo via Mongoose `src/server/hero.service.js` 59 | 1. Modify `src/server/routes.js` to use the new `hero.service.js` 60 | 1. Restart the node process in the VS Code debugger. 61 | 1. Open Postman and paste perform a *GET* http request on http://localhost:3000/api/heroes. You should see the following JSON results 62 | 1. Open the app at http://localhost:3000. You should see the app no longer has any heroes, because we are hitting the new database. 63 | 64 | ## Create the post, put and delete methods in the node server 65 | 66 | ### Create the Hero Post 67 | 68 | 1. In the `src/server/hero.service`, create the post method and the helper functions. 69 | 1. In the `src/server/routes.js`, call the post function 70 | 1. Restart the node process in the VS Code debugger. 71 | 1. Open the app at http://localhost:3000. Add a hero 72 | 73 | ### Create the PUT and DELETE 74 | 75 | 1. In the `src/server/hero.service`, create the post method and the helper functions. 76 | 1. In the `src/server/routes.js`, call the post function 77 | 1. Restart the node process in the VS Code debugger. 78 | 1. Open the app at http://localhost:3000. Get, Add, Update, Delete ! 79 | -------------------------------------------------------------------------------- /VIDEOS.md: -------------------------------------------------------------------------------- 1 | # Learning to Build MEAN Apps with Cosmos DB 2 | 3 | ## Overview 4 | 5 | This video series shows how to build MEAN apps using Cosmos DB. Each video in the series will be a couple of minutes and show how each part of the application works. 6 | 7 | ## Videos 8 | 9 | ### Part 1 - Introduction 10 | 11 | Cosmos DB makes it easy to step right in where I used to use Mongo, because it lets me use the same exact APIs I used with Mongo. Are you using the mongo npm module? No problem. Do you prefer mongoose? That works too. I just love that I don't have to change how I work! This video takes a look at what we'll build. 12 | 13 | [![MEAN and Cosmos DB - Part 1](https://img.youtube.com/vi/vlZRP0mDabM/0.jpg)](https://www.youtube.com/watch?v=vlZRP0mDabM) 14 | 15 | ### Part 2 - Node.js and Express 16 | 17 | One of the best ways to create a MEAN app is to start with the [Angular CLI](https://github.com/angular/angular-cli) to generate a starting point. We'll use this and extend it to include Node.js and Express.js, to serve our future APIs to the Angular app. Then we'll test out an API to make sure it works. 18 | 19 | [![MEAN and Cosmos DB - Part 2](https://img.youtube.com/vi/lIwJIYcGSUg/0.jpg)](https://www.youtube.com/watch?v=lIwJIYcGSUg) 20 | 21 | ### Part 3 - Angular and Express APIs 22 | 23 | I create the UI with Angular and connect to the Express APIs. I use the [Angular CLI](https://github.com/angular/angular-cli) to generate a the component and service the app needs. Then I build the app and show it in the browser. 24 | 25 | [![MEAN and Cosmos DB - Part 3](https://img.youtube.com/vi/MnxHuqcJVoM/0.jpg)](https://www.youtube.com/watch?v=MnxHuqcJVoM) 26 | 27 | ### Part 4 - Creating Cosmos DB 28 | 29 | Using the Azure CLI, I create the Cosmos DB account to represent a MongoDB model database and deploy it to Azure. Then I show how to view what we created in the Azure portal. 30 | 31 | [![MEAN and Cosmos DB - Part 4](https://img.youtube.com/vi/hfUM-AbOh94/0.jpg)](https://www.youtube.com/watch?v=hfUM-AbOh94) 32 | 33 | ### Part 5 - Querying Cosmos DB 34 | 35 | This next video shows how to connect to the MongoDB database with Azure Cosmos DB, using Mongoose, and query it for data. 36 | 37 | [![MEAN and Cosmos DB - Part 5](https://img.youtube.com/vi/sI5hw6KPPXI/0.jpg)](https://www.youtube.com/watch?v=sI5hw6KPPXI) 38 | 39 | ### Part 6 - POST, PUT, and DELETE 40 | 41 | This next video shows how to POST, PUT, and DELETE to the MongoDB database with Azure Cosmos DB, using Mongoose. 42 | 43 | [![MEAN and Cosmos DB - Part 6](https://img.youtube.com/vi/Y5mdAlFGZjc/0.jpg)](https://www.youtube.com/watch?v=Y5mdAlFGZjc) 44 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AngularCosmosdbPage } from './app.po'; 2 | 3 | describe('angular-cosmosdb App', () => { 4 | let page: AngularCosmosdbPage; 5 | 6 | beforeEach(() => { 7 | page = new AngularCosmosdbPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to toh!!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AngularCosmosdbPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cosmosdb", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "node src/server/index.js", 8 | "start-ng": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^4.2.4", 17 | "@angular/common": "^4.2.4", 18 | "@angular/compiler": "^4.2.4", 19 | "@angular/core": "^4.2.4", 20 | "@angular/forms": "^4.2.4", 21 | "@angular/http": "^4.2.4", 22 | "@angular/platform-browser": "^4.2.4", 23 | "@angular/platform-browser-dynamic": "^4.2.4", 24 | "@angular/router": "^4.2.4", 25 | "core-js": "^2.4.1", 26 | "rxjs": "^5.4.2", 27 | "zone.js": "^0.8.14", 28 | "express": "^4.15.3", 29 | "mongoose": "^4.11.1" 30 | }, 31 | "devDependencies": { 32 | "@angular/cli": "1.3.1", 33 | "@angular/compiler-cli": "^4.2.4", 34 | "@angular/language-service": "^4.2.4", 35 | "@types/jasmine": "~2.5.53", 36 | "@types/jasminewd2": "~2.0.2", 37 | "@types/node": "~6.0.60", 38 | "codelyzer": "~3.1.1", 39 | "jasmine-core": "~2.6.2", 40 | "jasmine-spec-reporter": "~4.1.0", 41 | "karma": "~1.7.0", 42 | "karma-chrome-launcher": "~2.1.1", 43 | "karma-cli": "~1.0.1", 44 | "karma-coverage-istanbul-reporter": "^1.2.1", 45 | "karma-jasmine": "~1.1.0", 46 | "karma-jasmine-html-reporter": "^0.2.2", 47 | "protractor": "~5.1.2", 48 | "ts-node": "~3.2.0", 49 | "tslint": "~5.3.2", 50 | "typescript": "~2.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/client/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 |

7 | Heroes 8 |

9 |
10 | 11 | ` 12 | }) 13 | export class AppComponent {} 14 | -------------------------------------------------------------------------------- /src/client/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { HeroService } from './hero.service'; 8 | import { HeroesComponent } from './heroes.component'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | HeroesComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | FormsModule, 18 | HttpClientModule 19 | ], 20 | providers: [HeroService], 21 | bootstrap: [AppComponent] 22 | }) 23 | export class AppModule { } 24 | -------------------------------------------------------------------------------- /src/client/app/hero.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | import { Hero } from './hero'; 5 | 6 | const api = '/api'; 7 | 8 | @Injectable() 9 | export class HeroService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getHeroes() { 13 | return this.http.get>(`${api}/heroes`) 14 | } 15 | 16 | deleteHero(hero: Hero) { 17 | return this.http.delete(`${api}/hero/${hero.id}`); 18 | } 19 | 20 | addHero(hero: Hero) { 21 | return this.http.post(`${api}/hero/`, hero); 22 | } 23 | 24 | updateHero(hero: Hero) { 25 | return this.http.put(`${api}/hero/${hero.id}`, hero); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/client/app/hero.ts: -------------------------------------------------------------------------------- 1 | export class Hero { 2 | id: number; 3 | name: string; 4 | saying: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/client/app/heroes.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 | 5 |
    6 |
    {{hero.id}}
    7 |
    {{hero.name}}
    8 |
    {{hero.saying}}
    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 | -------------------------------------------------------------------------------- /src/client/app/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Hero } from './hero'; 4 | import { HeroService } from './hero.service'; 5 | 6 | @Component({ 7 | selector: 'app-heroes', 8 | templateUrl: './heroes.component.html' 9 | }) 10 | export class HeroesComponent implements OnInit { 11 | addingHero = false; 12 | heroes: any = []; 13 | selectedHero: Hero; 14 | 15 | constructor(private heroService: HeroService) {} 16 | 17 | ngOnInit() { 18 | this.getHeroes(); 19 | } 20 | 21 | cancel() { 22 | this.addingHero = false; 23 | this.selectedHero = null; 24 | } 25 | 26 | deleteHero(hero: Hero) { 27 | this.heroService.deleteHero(hero).subscribe(res => { 28 | this.heroes = this.heroes.filter(h => h !== hero); 29 | if (this.selectedHero === hero) { 30 | this.selectedHero = null; 31 | } 32 | }); 33 | } 34 | 35 | getHeroes() { 36 | return this.heroService.getHeroes().subscribe(heroes => { 37 | this.heroes = heroes; 38 | }); 39 | } 40 | 41 | enableAddMode() { 42 | this.addingHero = true; 43 | this.selectedHero = new Hero(); 44 | } 45 | 46 | onSelect(hero: Hero) { 47 | this.addingHero = false; 48 | this.selectedHero = hero; 49 | } 50 | 51 | save() { 52 | if (this.addingHero) { 53 | this.heroService.addHero(this.selectedHero).subscribe(hero => { 54 | this.addingHero = false; 55 | this.selectedHero = null; 56 | this.heroes.push(hero); 57 | }); 58 | } else { 59 | this.heroService.updateHero(this.selectedHero).subscribe(hero => { 60 | this.addingHero = false; 61 | this.selectedHero = null; 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/client/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-cosmosdb/ea0021bcc627efbf9a24d056c91a198965709dd6/src/client/assets/.gitkeep -------------------------------------------------------------------------------- /src/client/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/client/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-cosmosdb/ea0021bcc627efbf9a24d056c91a198965709dd6/src/client/favicon.ico -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Cosmos DB 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/client/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/client/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /src/client/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | * { 4 | font-family: Arial; 5 | } 6 | h2 { 7 | color: #444; 8 | font-weight: lighter; 9 | } 10 | body { 11 | margin: 2em; 12 | } 13 | 14 | body, 15 | input[text], 16 | button { 17 | color: #888; 18 | // font-family: Cambria, Georgia; 19 | } 20 | button { 21 | font-size: 14px; 22 | font-family: Arial; 23 | background-color: #eee; 24 | border: none; 25 | padding: 5px 10px; 26 | border-radius: 4px; 27 | cursor: pointer; 28 | cursor: hand; 29 | &:hover { 30 | background-color: #cfd8dc; 31 | } 32 | &.delete-button { 33 | float: right; 34 | background-color: gray !important; 35 | background-color: rgb(216, 59, 1) !important; 36 | color: white; 37 | padding: 4px; 38 | position: relative; 39 | font-size: 12px; 40 | } 41 | } 42 | div { 43 | margin: .1em; 44 | } 45 | 46 | .selected { 47 | background-color: #cfd8dc !important; 48 | background-color: rgb(0, 120, 215) !important; 49 | color: white; 50 | } 51 | 52 | .heroes { 53 | float: left; 54 | margin: 0 0 2em 0; 55 | list-style-type: none; 56 | padding: 0; 57 | li { 58 | cursor: pointer; 59 | position: relative; 60 | left: 0; 61 | background-color: #eee; 62 | margin: .5em; 63 | padding: .5em; 64 | height: 3.0em; 65 | border-radius: 4px; 66 | width: 17em; 67 | &:hover { 68 | color: #607d8b; 69 | color: rgb(0, 120, 215); 70 | background-color: #ddd; 71 | left: .1em; 72 | } 73 | &.selected:hover { 74 | /*background-color: #BBD8DC !important;*/ 75 | color: white; 76 | } 77 | } 78 | .text { 79 | position: relative; 80 | top: -3px; 81 | } 82 | .saying { 83 | margin: 5px 0; 84 | } 85 | .name { 86 | font-weight: bold; 87 | } 88 | .badge { 89 | /* display: inline-block; */ 90 | float: left; 91 | font-size: small; 92 | color: white; 93 | padding: 0.7em 0.7em 0 0.5em; 94 | background-color: #607d8b; 95 | background-color: rgb(0, 120, 215); 96 | background-color:rgb(134, 183, 221); 97 | line-height: 1em; 98 | position: relative; 99 | left: -1px; 100 | top: -4px; 101 | height: 3.0em; 102 | margin-right: .8em; 103 | border-radius: 4px 0 0 4px; 104 | width: 1.2em; 105 | } 106 | } 107 | 108 | .header-bar { 109 | background-color: rgb(0, 120, 215); 110 | height: 4px; 111 | margin-top: 10px; 112 | margin-bottom: 10px; 113 | } 114 | 115 | label { 116 | display: inline-block; 117 | width: 4em; 118 | margin: .5em 0; 119 | color: #888; 120 | &.value { 121 | margin-left: 10px; 122 | font-size: 14px; 123 | } 124 | } 125 | 126 | input { 127 | height: 2em; 128 | font-size: 1em; 129 | padding-left: .4em; 130 | &::placeholder { 131 | color: lightgray; 132 | font-weight: normal; 133 | font-size: 12px; 134 | letter-spacing: 3px; 135 | } 136 | } 137 | 138 | .editarea { 139 | float: left; 140 | input { 141 | margin: 4px; 142 | height: 20px; 143 | color: rgb(0, 120, 215); 144 | } 145 | button { 146 | margin: 8px; 147 | } 148 | .editfields { 149 | margin-left: 12px; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/client/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/client/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/server/env/example-environment.js: -------------------------------------------------------------------------------- 1 | const cosmosPort = 1234; // replace with your port 2 | const dbName = 'your-cosmos-db-name-goes-here'; 3 | const key = 'your-key-goes-here'; 4 | 5 | module.exports = { 6 | dbName, 7 | key, 8 | cosmosPort 9 | }; 10 | -------------------------------------------------------------------------------- /src/server/hero.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const heroSchema = new Schema( 6 | { 7 | id: { type: Number, required: true, unique: true }, 8 | name: String, 9 | saying: String 10 | }, 11 | { 12 | collection: 'heroes', 13 | read: 'nearest' 14 | } 15 | ); 16 | 17 | const Hero = mongoose.model('Hero', heroSchema); 18 | 19 | module.exports = Hero; 20 | -------------------------------------------------------------------------------- /src/server/hero.service.js: -------------------------------------------------------------------------------- 1 | const Hero = require('./hero.model'); 2 | const ReadPreference = require('mongodb').ReadPreference; 3 | 4 | require('./mongo').connect(); 5 | 6 | function getHeroes(req, res) { 7 | const docquery = Hero.find({}).read(ReadPreference.NEAREST); 8 | docquery 9 | .exec() 10 | .then(heroes => { 11 | res.status(200).json(heroes); 12 | }) 13 | .catch(error => { 14 | res.status(500).send(error); 15 | return; 16 | }); 17 | } 18 | 19 | function postHero(req, res) { 20 | const originalHero = { id: req.body.id, name: req.body.name, saying: req.body.saying }; 21 | const hero = new Hero(originalHero); 22 | hero.save(error => { 23 | if (checkServerError(res, error)) return; 24 | res.status(201).json(hero); 25 | console.log('Hero created successfully!'); 26 | }); 27 | } 28 | 29 | function putHero(req, res) { 30 | const originalHero = { 31 | id: parseInt(req.params.id, 10), 32 | name: req.body.name, 33 | saying: req.body.saying 34 | }; 35 | Hero.findOne({ id: originalHero.id }, (error, hero) => { 36 | if (checkServerError(res, error)) return; 37 | if (!checkFound(res, hero)) return; 38 | 39 | hero.name = originalHero.name; 40 | hero.saying = originalHero.saying; 41 | hero.save(error => { 42 | if (checkServerError(res, error)) return; 43 | res.status(200).json(hero); 44 | console.log('Hero updated successfully!'); 45 | }); 46 | }); 47 | } 48 | 49 | function deleteHero(req, res) { 50 | const id = parseInt(req.params.id, 10); 51 | Hero.findOneAndRemove({ id: id }) 52 | .then(hero => { 53 | if (!checkFound(res, hero)) return; 54 | res.status(200).json(hero); 55 | console.log('Hero deleted successfully!'); 56 | }) 57 | .catch(error => { 58 | if (checkServerError(res, error)) return; 59 | }); 60 | } 61 | 62 | function checkServerError(res, error) { 63 | if (error) { 64 | res.status(500).send(error); 65 | return error; 66 | } 67 | } 68 | 69 | function checkFound(res, hero) { 70 | if (!hero) { 71 | res.status(404).send('Hero not found.'); 72 | return; 73 | } 74 | return hero; 75 | } 76 | 77 | module.exports = { 78 | getHeroes, 79 | postHero, 80 | putHero, 81 | deleteHero 82 | }; 83 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const routes = require('./routes'); 4 | 5 | const publicweb = './publicweb'; 6 | const app = express(); 7 | 8 | app.use(bodyParser.json()); 9 | app.use(bodyParser.urlencoded({ extended: false })); 10 | app.use(express.static(publicweb)); 11 | console.log(`serving ${publicweb}`); 12 | 13 | app.use('/api', routes); 14 | 15 | app.get('*', (req, res) => { 16 | res.sendFile(`index.html`, { root: publicweb }); 17 | }); 18 | 19 | const port = process.env.PORT || '3000'; 20 | app.listen(port, () => console.log(`API running on localhost:${port}`)); 21 | -------------------------------------------------------------------------------- /src/server/mongo.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | /** 3 | * Set to Node.js native promises 4 | * Per http://mongoosejs.com/docs/promises.html 5 | */ 6 | mongoose.Promise = global.Promise; 7 | 8 | const env = require('./env/environment'); 9 | 10 | // eslint-disable-next-line max-len 11 | const mongoUri = `mongodb://${env.dbName}:${env.key}@${env.dbName}.documents.azure.com:${env.cosmosPort}/?ssl=true`; //&replicaSet=globaldb`; 12 | 13 | function connect() { 14 | mongoose.set('debug', true); 15 | return mongoose.connect(mongoUri, { useMongoClient: true }); 16 | } 17 | 18 | module.exports = { 19 | connect, 20 | mongoose 21 | }; 22 | -------------------------------------------------------------------------------- /src/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cosmosdb", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "node index.js" 7 | }, 8 | "private": true, 9 | "dependencies": { 10 | "body-parser": "^1.17.2", 11 | "express": "^4.15.3", 12 | "mongoose": "^4.11.1" 13 | }, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /src/server/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const heroService = require('./hero.service'); 5 | 6 | router.get('/heroes', (req, res) => { 7 | heroService.getHeroes(req, res); 8 | }); 9 | 10 | router.post('/hero', (req, res) => { 11 | heroService.postHero(req, res); 12 | }); 13 | 14 | router.put('/hero/:id', (req, res) => { 15 | heroService.putHero(req, res); 16 | }); 17 | 18 | router.delete('/hero/:id', (req, res) => { 19 | heroService.deleteHero(req, res); 20 | }); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | "static-before-instance", 35 | "variables-before-functions" 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-empty-interface": true, 52 | "no-eval": true, 53 | "no-inferrable-types": [ 54 | true, 55 | "ignore-params" 56 | ], 57 | "no-misused-new": true, 58 | "no-non-null-assertion": true, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-string-throw": true, 62 | "no-switch-case-fall-through": true, 63 | "no-trailing-whitespace": true, 64 | "no-unnecessary-initializer": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "object-literal-sort-keys": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "prefer-const": true, 77 | "quotemark": [ 78 | true, 79 | "single" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | "always" 84 | ], 85 | "triple-equals": [ 86 | true, 87 | "allow-null-check" 88 | ], 89 | "typedef-whitespace": [ 90 | true, 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | } 98 | ], 99 | "typeof-compare": true, 100 | "unified-signatures": true, 101 | "variable-name": false, 102 | "whitespace": [ 103 | true, 104 | "check-branch", 105 | "check-decl", 106 | "check-operator", 107 | "check-separator", 108 | "check-type" 109 | ], 110 | "directive-selector": [ 111 | true, 112 | "attribute", 113 | "app", 114 | "camelCase" 115 | ], 116 | "component-selector": [ 117 | true, 118 | "element", 119 | "app", 120 | "kebab-case" 121 | ], 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true, 131 | "no-access-missing-member": true, 132 | "templates-use-public": true, 133 | "invoke-injectable": true 134 | } 135 | } 136 | --------------------------------------------------------------------------------