├── .angular-cli.json ├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .snyk ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── TUTORIAL.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 │ └── 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", 10 | "assets": ["assets", "favicon.ico"], 11 | "index": "index.html", 12 | "main": "main.ts", 13 | "polyfills": "polyfills.ts", 14 | "test": "test.ts", 15 | "tsconfig": "tsconfig.app.json", 16 | "testTsconfig": "tsconfig.spec.json", 17 | "prefix": "app", 18 | "styles": ["styles.scss"], 19 | "scripts": [], 20 | "environmentSource": "environments/environment.ts", 21 | "environments": { 22 | "dev": "environments/environment.ts", 23 | "prod": "environments/environment.prod.ts" 24 | } 25 | } 26 | ], 27 | "e2e": { 28 | "protractor": { 29 | "config": "./protractor.conf.js" 30 | } 31 | }, 32 | "lint": [ 33 | { 34 | "project": "src/client/tsconfig.app.json", 35 | "exclude": "**/node_modules/**" 36 | }, 37 | { 38 | "project": "src/client/tsconfig.spec.json", 39 | "exclude": "**/node_modules/**" 40 | }, 41 | { 42 | "project": "e2e/tsconfig.e2e.json", 43 | "exclude": "**/node_modules/**" 44 | } 45 | ], 46 | "test": { 47 | "karma": { 48 | "config": "./karma.conf.js" 49 | } 50 | }, 51 | "defaults": { 52 | "styleExt": "scss", 53 | "component": { 54 | "spec": false, 55 | "inlineStyle": true, 56 | "inlineTemplate": true 57 | }, 58 | "directive": { 59 | "spec": false 60 | }, 61 | "class": { 62 | "spec": false 63 | }, 64 | "guard": { 65 | "spec": false 66 | }, 67 | "module": { 68 | "spec": false 69 | }, 70 | "pipe": { 71 | "spec": false 72 | }, 73 | "service": { 74 | "spec": false 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | 8 | - [ ] Yes 9 | - [ ] No 10 | 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | 17 | - [ ] Bugfix 18 | - [ ] Feature 19 | - [ ] Code style update (formatting, local variables) 20 | - [ ] Refactoring (no functional changes, no api changes) 21 | - [ ] Documentation content changes 22 | - [ ] Other... Please describe: 23 | 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore server configuration 2 | src/server/env/environment.js 3 | src/server/env/development.js 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # See http://help.github.com/ignore-files/ for more about ignoring files. 65 | 66 | # compiled output 67 | /dist 68 | /tmp 69 | /out-tsc 70 | 71 | # IDEs and editors 72 | /.idea 73 | .project 74 | .classpath 75 | .c9/ 76 | *.launch 77 | .settings/ 78 | *.sublime-workspace 79 | 80 | # IDE - VSCode 81 | .vscode/* 82 | !.vscode/settings.json 83 | !.vscode/tasks.json 84 | !.vscode/launch.json 85 | !.vscode/extensions.json 86 | 87 | # misc 88 | /.sass-cache 89 | /connect.lock 90 | /coverage 91 | /libpeerconnection.log 92 | npm-debug.log 93 | testem.log 94 | /typings 95 | 96 | # e2e 97 | /e2e/*.js 98 | /e2e/*.map 99 | 100 | # System Files 101 | .DS_Store 102 | Thumbs.db 103 | 104 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.12.0 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## angular-cosmosdb Changelog 2 | 3 | 4 | # 1.0.1 (2018-06-26) 5 | 6 | *Bug Fixes* 7 | * Fix [8436](https://github.com/MicrosoftDocs/azure-docs/issues/8436) 8 | - Error on insert due to duplicate key. Mongoose reserves the key `id` and we're attempting to use that within our Hero model. Changed our model to have a key of `uid` instead. 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to angular-cosmosdb 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6-alpine 2 | # FROM mhart/alpine-node 3 | 4 | # Create app directory 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | COPY package.json /usr/src/app/ 10 | RUN npm install 11 | 12 | # Bundle app source 13 | COPY dist/ /usr/src/app/dist 14 | COPY src/server/ /usr/src/app/ 15 | 16 | ENV PORT 80 17 | # EXPOSE 3000 18 | 19 | CMD [ "node", "index.js" ] 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - typescript 5 | - javascript 6 | - html 7 | products: 8 | - azure 9 | description: "You can watch me build the app as part of my series here" 10 | urlFragment: angular-cosmosdb 11 | --- 12 | 13 | # Angular Cosmos DB 14 | 15 | by [John Papa](http://twitter.com/john_papa) 16 | 17 | You can [watch me build the app as part of my series here](https://johnpapa.net/angular-cosmosdb-1/) 18 | 19 | You can [view all videos together here](/VIDEOS.md) 20 | 21 | [Learn more about developing Node.js apps with Azure's cloud services here](https://docs.microsoft.com/en-us/nodejs/azure) 22 | 23 | ## Requirements 24 | 25 | 1. Install the Angular CLI 26 | 27 | ```bash 28 | npm install -g @angular/cli 29 | ``` 30 | 31 | 2. Install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) 32 | 33 | 3. Set up a Cosmos DB server with a MongoDB database. Make sure you note the name of the Azure Cosmos DB account, the name of the database, the primary password and the port. You can find all this information in the [Azure portal](https://portal.azure.com). 34 | 35 | 36 | ## Getting Started 37 | 38 | 1. Clone this repository 39 | 40 | ```bash 41 | git clone https://github.com/johnpapa/angular-cosmosdb.git 42 | cd angular-cosmosdb 43 | ``` 44 | 45 | 2. Install the npm packages 46 | 47 | ```bash 48 | npm i 49 | ``` 50 | 51 | 3. Configure Cosmos DB server settings 52 | 53 | Rename the `example-environment.js` file to `environment.js` in the `server/env/` folder and update it with your Cosmos DB settings. Replace the account, database name, key, and port with your specific configuration. 54 | 55 | ```javascript 56 | // server/env/environment.js 57 | 58 | module.exports = { 59 | accountName: 'your-cosmosdb-account-name-goes-here', 60 | databaseName: 'your-cosmosdb-database-name-goes-here', 61 | key: 'your-key-goes-here', 62 | port: 10255 63 | }; 64 | ``` 65 | 66 | ## Running the app 67 | 68 | 1. Build the Angular app 69 | 70 | ```bash 71 | ng build 72 | ``` 73 | 74 | 2. Launch the server 75 | 76 | ```bash 77 | node src/server/index.js 78 | ``` 79 | 80 | 3. Open the browser to http://localhost:3000 81 | 82 | ## Problems or Suggestions 83 | 84 | [Open an issue here](https://github.com/johnpapa/angular-cosmos/issues) 85 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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": "1.0.1", 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 | "express": "^4.16.0", 27 | "mongoose": "^4.11.14", 28 | "rxjs": "^5.4.2", 29 | "zone.js": "^0.8.14" 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": "^2.0.2", 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.3.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 | Angular 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.uid}`); 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.uid}`, hero); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/client/app/hero.ts: -------------------------------------------------------------------------------- 1 | export class Hero { 2 | uid: number; 3 | name: string; 4 | saying: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/client/app/heroes.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 | 5 |
    6 |
    {{hero.uid}}
    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/Azure-Samples/angular-cosmosdb/73549bff17f095f36a6bb5288709852f7f8d68dc/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/Azure-Samples/angular-cosmosdb/73549bff17f095f36a6bb5288709852f7f8d68dc/src/client/favicon.ico -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularCosmosdb 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 | module.exports = { 2 | accountName: 'your-cosmosdb-account-name-goes-here', 3 | databaseName: 'admin', 4 | key: 'your-key-goes-here', 5 | port: 10255 6 | }; 7 | -------------------------------------------------------------------------------- /src/server/hero.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const heroSchema = new Schema( 6 | { 7 | uid: { 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 = { uid: req.body.uid, 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 | uid: parseInt(req.params.uid, 10), 32 | name: req.body.name, 33 | saying: req.body.saying 34 | }; 35 | Hero.findOne({ uid: originalHero.uid }, (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 uid = parseInt(req.params.uid, 10); 51 | Hero.findOneAndRemove({ uid: uid }) 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 path = require('path'); 3 | const bodyParser = require('body-parser'); 4 | const routes = require('./routes'); 5 | 6 | const root = './'; 7 | const app = express(); 8 | 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({ extended: false })); 11 | app.use(express.static(path.join(root, 'dist'))); 12 | app.use('/api', routes); 13 | app.get('*', (req, res) => { 14 | res.sendFile('dist/index.html', {root: root}); 15 | }); 16 | 17 | const port = process.env.PORT || '3000'; 18 | app.listen(port, () => console.log(`API running on localhost:${port}`)); 19 | -------------------------------------------------------------------------------- /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.accountName}:${env.key}@${env.accountName}.documents.azure.com:${env.port}/${ 12 | env.databaseName 13 | }?ssl=true`; 14 | 15 | function connect() { 16 | mongoose.set('debug', true); 17 | return mongoose.connect( 18 | mongoUri, 19 | { 20 | useMongoClient: true 21 | } 22 | ); 23 | } 24 | 25 | module.exports = { 26 | connect, 27 | mongoose 28 | }; 29 | -------------------------------------------------------------------------------- /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/:uid', (req, res) => { 15 | heroService.putHero(req, res); 16 | }); 17 | 18 | router.delete('/hero/:uid', (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 | --------------------------------------------------------------------------------