├── .gitignore ├── README.md ├── code ├── .editorconfig ├── .gitignore ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── cordova-plugins │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── README.md ├── application.html ├── client │ ├── controllers │ │ ├── authenticated │ │ │ ├── api-key.js │ │ │ └── header.js │ │ └── public │ │ │ ├── login.js │ │ │ ├── recover-password.js │ │ │ ├── reset-password.js │ │ │ └── signup.js │ ├── helpers │ │ └── helpers-ui.js │ ├── includes │ │ └── _header.html │ ├── layouts │ │ └── layout-default.html │ ├── routes │ │ ├── hooks.js │ │ ├── routes-authenticated.js │ │ ├── routes-global.js │ │ └── routes-public.js │ ├── stylesheets │ │ └── sass │ │ │ ├── application.scss │ │ │ ├── globals │ │ │ └── _extends.scss │ │ │ └── views │ │ │ └── public │ │ │ └── _login.scss │ └── views │ │ ├── authenticated │ │ ├── api-key.html │ │ └── index.html │ │ └── public │ │ ├── loading.html │ │ ├── login.html │ │ ├── not-found.html │ │ ├── recover-password.html │ │ ├── reset-password.html │ │ └── signup.html ├── collections │ ├── api-keys.js │ ├── pizza.js │ └── users.js ├── package.json ├── packages.json ├── packages │ └── npm-container │ │ ├── index.js │ │ └── package.js ├── server │ ├── admin │ │ ├── startup-functions │ │ │ ├── _test-data.js │ │ │ ├── browser-policies.js │ │ │ └── test-accounts.js │ │ └── startup.js │ ├── api │ │ ├── config │ │ │ └── api.js │ │ └── resources │ │ │ └── pizza.js │ ├── email │ │ └── templates │ │ │ └── reset-password.js │ ├── methods │ │ ├── insert │ │ │ └── api-key.js │ │ ├── read │ │ │ └── example.js │ │ ├── remove │ │ │ └── example.js │ │ ├── update │ │ │ └── api-key.js │ │ └── utility │ │ │ └── example.js │ └── publications │ │ └── api-key.js ├── settings-development.json ├── smart.json └── smart.lock └── writing-an-api.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Tutorial: Writing a RESTful API 2 | 3 | undefined 4 | 5 | [Read this tutorial on The Meteor Chef](https://themeteorchef.com/tutorials/writing-an-api) 6 | 7 | [Download the source (.zip)](https://github.com/themeteorchef/writing-an-api/archive/master.zip) 8 | 9 | --- 10 | 11 | **Need help with this?** [Sign up for a Mentorship appointment](https://themeteorchef.com/mentorship?readme=writing-an-api) and get 1-on-1 help. 12 | 13 | --- 14 | 15 | _The code for this tutorial is licensed under the [MIT License](http://opensource.org/licenses/MIT)_. 16 | -------------------------------------------------------------------------------- /code/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig – Follow the rules, hippie. 2 | # Make sure to install the plugin for your editor: http://editorconfig.org/#download (we use SublimeText or Atom). 3 | # http://editorconfig.org 4 | 5 | # Root 6 | root = true 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 2 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.js] 17 | indent_size = 2 18 | 19 | [*.coffee] 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | -------------------------------------------------------------------------------- /code/.gitignore: -------------------------------------------------------------------------------- 1 | # Operating System 2 | .DS_Store 3 | 4 | # Configuration 5 | settings-production.json 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /code/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /code/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /code/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1ipv1tp12xzfzhba088c 8 | -------------------------------------------------------------------------------- /code/.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | standard-app-packages 7 | underscore 8 | accounts-password 9 | accounts-base 10 | jquery 11 | check 12 | audit-argument-checks 13 | iron:router 14 | themeteorchef:jquery-validation 15 | twbs:bootstrap 16 | browser-policy 17 | meteorhacks:npm 18 | 19 | 20 | npm-container 21 | themeteorchef:bert 22 | meteorhacks:ssr 23 | fourseven:scss 24 | http 25 | -------------------------------------------------------------------------------- /code/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /code/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.2 2 | -------------------------------------------------------------------------------- /code/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | accounts-password@1.1.1 3 | audit-argument-checks@1.0.3 4 | autoupdate@1.2.1 5 | base64@1.0.3 6 | binary-heap@1.0.3 7 | blaze@2.1.2 8 | blaze-tools@1.0.3 9 | boilerplate-generator@1.0.3 10 | browser-policy@1.0.4 11 | browser-policy-common@1.0.3 12 | browser-policy-content@1.0.4 13 | browser-policy-framing@1.0.4 14 | callback-hook@1.0.3 15 | check@1.0.5 16 | ddp@1.1.0 17 | deps@1.0.7 18 | ejson@1.0.6 19 | email@1.0.6 20 | fastclick@1.0.3 21 | fourseven:scss@2.1.1 22 | geojson-utils@1.0.3 23 | html-tools@1.0.4 24 | htmljs@1.0.4 25 | http@1.1.0 26 | id-map@1.0.3 27 | iron:controller@1.0.8 28 | iron:core@1.0.8 29 | iron:dynamic-template@1.0.8 30 | iron:layout@1.0.8 31 | iron:location@1.0.9 32 | iron:middleware-stack@1.0.9 33 | iron:router@1.0.9 34 | iron:url@1.0.9 35 | jquery@1.11.3_2 36 | json@1.0.3 37 | launch-screen@1.0.2 38 | livedata@1.0.13 39 | localstorage@1.0.3 40 | logging@1.0.7 41 | meteor@1.1.6 42 | meteor-platform@1.2.2 43 | meteorhacks:async@1.0.0 44 | meteorhacks:npm@1.3.0 45 | meteorhacks:ssr@2.1.2 46 | minifiers@1.1.5 47 | minimongo@1.0.8 48 | mobile-status-bar@1.0.3 49 | mongo@1.1.0 50 | npm-bcrypt@0.7.8_2 51 | npm-container@1.0.0 52 | observe-sequence@1.0.6 53 | ordered-dict@1.0.3 54 | random@1.0.3 55 | reactive-dict@1.1.0 56 | reactive-var@1.0.5 57 | reload@1.1.3 58 | retry@1.0.3 59 | routepolicy@1.0.5 60 | service-configuration@1.0.4 61 | session@1.1.0 62 | sha@1.0.3 63 | spacebars@1.0.6 64 | spacebars-compiler@1.0.6 65 | srp@1.0.3 66 | standard-app-packages@1.0.5 67 | templating@1.1.1 68 | themeteorchef:bert@1.1.0 69 | themeteorchef:jquery-validation@1.14.0 70 | tracker@1.0.7 71 | twbs:bootstrap@3.3.5 72 | ui@1.0.6 73 | underscore@1.0.3 74 | url@1.0.4 75 | webapp@1.2.0 76 | webapp-hashing@1.0.3 77 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | # The Meteor Chef - Base (@1.1.0.2) 2 | A starting point for Meteor apps. 3 | 4 | Base is the lazy person's starter kit for Meteor. It includes some commonly used packages (biased to The Meteor Chef) and code for common functionality. This is the starter kit for all Meteor Chef recipes. 5 | 6 | For more detail on updates, [read the Changelog](https://github.com/themeteorchef/base/wiki/Changelog). If you're interested in contributing to Base, checkout the [Contribution wiki](https://github.com/themeteorchef/base/wiki/Contributing-to-Base) to get started. 7 | 8 | ### Packages Included 9 | - Accounts (Base) - `meteor add accounts-base` 10 | - Accounts (Password) - `meteor add accounts-password` 11 | - Audit Argument Checks - `meteor add audit-argument-checks` 12 | - Bert - `meteor add themeteorchef:bert` 13 | - Bootstrap 3 - `meteor add twbs:bootstrap` 14 | - Browser Policy - `meteor add browser-policy` 15 | - Check - `meteor add check` 16 | - Iron Router - `meteor add iron:router` 17 | - jQuery - `meteor add jquery` 18 | - jQuery Validation - `meteor add themeteorchef:jquery-validation` 19 | - NPM - `meteor add meteorhacks:npm` 20 | - Sass - `meteor add fourseven:scss` 21 | - SSR - `meteor add meteorhacks:ssr` 22 | - Underscore - `meteor add underscore` 23 | 24 | **Note:** Base also supports loading NPM packages using the `meteorhacks:npm` package. To load NPM packages, add name and version information to `/packages.json` and inside of your file (server only), load the package with `var package = Meteor.npmRequire('package-name');`. For more information, see the [meteorhacks:npm documentation](https://github.com/meteorhacks/npm/). 25 | 26 | ### File Structure 27 | Base comes with a pre-defined file structure common to all projects along with some skeleton files for getting started quickly. Here's what it looks like: 28 | 29 | ``` 30 | /root 31 | ---/.meteor 32 | ---/client 33 | ------/controllers 34 | ---------/authenticated 35 | ------------header.js 36 | ---------/public 37 | ------------login.js 38 | ------------recover-password.js 39 | ------------reset-password.js 40 | ------------signup.js 41 | ------/helpers 42 | ---------helpers-ui.js 43 | ------/includes 44 | ---------_header.html 45 | ------/layouts 46 | ---------layout-default.html 47 | ------/routes 48 | ---------hooks.js 49 | ---------routes-authenticated.js 50 | ---------routes-global.js 51 | ---------routes-public.js 52 | ------/stylesheets 53 | ---------/sass 54 | ------------/globals 55 | ---------------_extends.scss 56 | ------------/views 57 | ---------------/public 58 | ------------------_login.scss 59 | ---------application.scss 60 | -------/views 61 | ---------/authenticated 62 | ------------index.html 63 | ---------/public 64 | ------------loading.html 65 | ------------login.html 66 | ------------not-found.html 67 | ------------recover-password.html 68 | ------------reset-password.html 69 | ------------signup.html 70 | ---/collections 71 | ------example.js 72 | ------users.js 73 | ---/packages 74 | ------ (See List Above) 75 | ---/public 76 | ------/images 77 | ---/server 78 | ------/admin 79 | ---------/startup-functions 80 | ------------browser-policies.js 81 | ------------test-accounts.js 82 | ---------startup.js 83 | ------/email 84 | ---------/templates 85 | ------------reset-password.js 86 | ------/methods 87 | ---------/insert 88 | ------------example.js 89 | ---------/read 90 | ------------example.js 91 | ---------/remove 92 | ------------example.js 93 | ---------/update 94 | ------------example.js 95 | ---------/utility 96 | ------------example.js 97 | ------/publications 98 | ---------example.js 99 | ---.editorconfig 100 | ---.gitignore 101 | ---application.html 102 | ---package.json 103 | ---packages.json 104 | ---README.MD (this file) 105 | ---settings-development.json 106 | ---settings-production.json 107 | ---smart.json (added by Meteor) 108 | ---smart.lock (added by Meteor) 109 | ``` 110 | 111 | ### JavaScript & CSS 112 | Prior to v2.0.0, Base was written in CoffeeScript. At the request of the community, Base was ported _back_ to native JavaScript. 113 | 114 | CSS in Base is written using [Sass](http://sass-lang.com). 115 | 116 | ### Functionality 117 | 118 | ###### Configuration 119 | Base includes a pattern for managing your API keys, connection strings, and other configuration information using two files: `settings-development.json` and `settings-production.json`. This pattern separates your development and production configuration into two separate files for the sake of security. 120 | 121 | Per [Josh Owens' article](http://joshowens.me/environment-settings-and-security-with-meteor-js/), it's considered "bad practice" to check your production keys into your repo (private or otherwise). Base accounts for this by giving you two separate files, but also specifies that your `settings-production.json` file should be ignored by git in `.gitignore`. 122 | 123 | This means that keys that are only used for testing or development purposes can be placed in `settings-development.json`, while keys used in your production application should be placed in `settings-production.json`. Sharing and management of `settings-production.json` should be done on a person-to-person basis and _not_ made globally accessible. 124 | 125 | ###### Startup & Deployment 126 | A tip picked up from [Gerard Sychay at Differential](http://blog.differential.com/use-package-json-in-your-meteor-app-for-fun-profit/), Base makes use of the NPM `package.json` convention to make startup and deployment super easy. Within `package.json`, three scripts have been defined for you: 127 | 128 | 1. `npm start` - Starts your Meteor server locally with `settings-development.json` in tow. Equivalent to typing out `meteor --settings settings-development.json`. 129 | 2. `npm staging` - Deploys your Meteor app to a [Modulus](http://modulus.io) server defined as your "Staging" environment. This requires you to have a Modulus account set up and a server titled "Staging" set up. You can customize this to match your own naming conventions. This also automatically sets your `METEOR_SETTINGS` environment variable on Modulus equal to the contents of your `settings-development.json` file so you don't have to do it by hand. 130 | 3. `npm production` - Deploys your Meteor app to a [Modulus](http://modulus.io) server defined as your "Production" environment. This requires you to have a Modulus account set up and a server titled "Production" set up. You can customize this to match your own naming conventions. This also automatically sets your `METEOR_SETTINGS` environment variable on Modulus equal to the contents of your `settings-production.json` file so you don't have to do it by hand. 131 | 132 | ###### Bootstrap (@3.2.1) 133 | Base makes use of the [Bootstrap](http://getbootstrap.com) front-end Framework. It may not be your bag of chips and is *definitely not required*. If you want to swap it out, you'll need to unhook the markup in each of the included template files in `/client/views` and uninstall the `twbs:bootstrap` package by running `meteor remove twbs:bootstrap` in your terminal. 134 | 135 | In respect to UI, Base uses Bootstrap's `.navbar` element, as well as its `.container` and a few `.row`/`.col--` wrappers. You'll also find the `.btn` class and its modifiers (`.success, .warning, etc.`) in use throughout the app. All of these implementations are merely presentational and can be changed (or removed) as you see fit. 136 | 137 | ###### Basic Routing 138 | Base comes with a collection of pre-defined routes and templates for common functionality. Base also includes a set of common route filters for managing user access. Routes bundled include: 139 | 140 | ``` 141 | - / (Authenticated) 142 | - /login (Public) 143 | - /recover-password (Public) 144 | - /reset-password (Public) 145 | - /signup (Public) 146 | ``` 147 | 148 | A UI helper called `currentRoute` has been added to Base which allows you to add an `active` class to menu items in your navigation to reflect the user's current location. 149 | 150 | A collection of hooks has also been added to Base to control route access based on different conditions (e.g. whether a user is logged in or not). 151 | 152 | ###### Authentication 153 | Base includes a complete authentication pattern complete with: 154 | 155 | - Login (at /login) 156 | - Logout (no path, implemented as a dropdown item/click event in /client/controllers/header.js) 157 | - Password Recovery (at /recover-password and /reset-password) 158 | - Signup (at /signup) 159 | 160 | ###### Example Collection/Publication/Subscription 161 | Base includes a collection called `Example`, along with a publication and subscription pattern to show moving data from the server to the client. Publications are defined in `/server/publications/example.js` and a subscription is demonstrated on the `index` route in `/client/routes/routes-authenticated.js`. 162 | 163 | ###### Validation 164 | Base includes support for client-side validation via [jQuery Validation](http://jqueryvalidation.org). Validation is provided for all public templates: login, signup, recover password, and reset password. 165 | 166 | ###### Alerts 167 | Base includes support for fixed bar (top and bottom) and growl-style alerts on the client via [`themeteorchef:bert`](https://atmospherejs.com/themeteorchef/bert). 168 | 169 | ###### Automatic Admin User Creation 170 | When developing, having a handful of user accounts to test your application with can come in handy. Base comes with an automated account generation script located in `server/admin/startup.js` that creates accounts based on an array of specified users. **Note: by default this creates one Admin user on server startup, so make sure to customize or remove this user so the public can't access your app**. 171 | -------------------------------------------------------------------------------- /code/application.html: -------------------------------------------------------------------------------- 1 | 2 | Pizza API 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /code/client/controllers/authenticated/api-key.js: -------------------------------------------------------------------------------- 1 | Template.apiKey.onCreated(function(){ 2 | // Here, we're making use of Meteor's new template-level subscriptions. 3 | this.subscribe( "APIKey" ); 4 | }); 5 | 6 | Template.apiKey.helpers({ 7 | apiKey: function() { 8 | // Note: because we know our publication is already returning the key for 9 | // the current user, and we only expect it to return 1 key, we can do 10 | // a findOne here without any projections. Nice! 11 | var apiKey = APIKeys.findOne(); 12 | 13 | if ( apiKey ) { 14 | return apiKey.key; 15 | } 16 | } 17 | }); 18 | 19 | Template.apiKey.events({ 20 | 'click .regenerate-api-key': function(){ 21 | var userId = Meteor.userId(), 22 | confirmRegeneration = confirm( "Are you sure? This will invalidate your current key!" ); 23 | 24 | if ( confirmRegeneration ) { 25 | Meteor.call( "regenerateApiKey", userId, function( error, response ) { 26 | if ( error ) { 27 | Bert.alert( error.reason, "danger" ); 28 | } else { 29 | Bert.alert( "All done! You have a new API key.", "success" ); 30 | } 31 | }); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /code/client/controllers/authenticated/header.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller: Header 3 | * Template: /client/includes/_header.html 4 | */ 5 | 6 | /* 7 | * Created 8 | */ 9 | 10 | Template.header.onCreated(function(){ 11 | // Code to run when template is created goes here. 12 | }); 13 | 14 | /* 15 | * Rendered 16 | */ 17 | 18 | Template.header.onRendered(function() { 19 | // Code to run when template is rendered goes here. 20 | }); 21 | 22 | /* 23 | * Helpers 24 | */ 25 | 26 | Template.header.helpers({ 27 | example: function(){ 28 | // Code to run for helper function. 29 | } 30 | }); 31 | 32 | /* 33 | * Events 34 | */ 35 | 36 | Template.header.events({ 37 | 'click .logout': function(){ 38 | Meteor.logout(function(error){ 39 | if(error){ 40 | Bert.alert(error.reason, 'danger'); 41 | } else { 42 | Bert.alert('Succesfully logged out!', 'success'); 43 | } 44 | }); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /code/client/controllers/public/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller: Login 3 | * Template: /client/views/public/login.html 4 | */ 5 | 6 | /* 7 | * Created 8 | */ 9 | 10 | Template.login.onCreated(function(){ 11 | // Code to run when template is created goes here. 12 | }); 13 | 14 | /* 15 | * Rendered 16 | */ 17 | 18 | Template.login.onRendered(function(){ 19 | $('#application-login').validate({ 20 | rules: { 21 | emailAddress: { 22 | required: true, 23 | email: true 24 | }, 25 | password: { 26 | required: true 27 | } 28 | }, 29 | messages: { 30 | emailAddress: { 31 | required: "Please enter your email address to login.", 32 | email: "Please enter a valid email address." 33 | }, 34 | password: { 35 | required: "Please enter your password to login." 36 | } 37 | }, 38 | submitHandler: function(){ 39 | // Grab the user's details. 40 | user = { 41 | email: $('[name="emailAddress"]').val(), 42 | password: $('[name="password"]').val() 43 | } 44 | 45 | // Log the user in. 46 | Meteor.loginWithPassword(user.email, user.password, function(error){ 47 | if(error){ 48 | Bert.alert(error.reason, 'danger'); 49 | } else { 50 | Bert.alert('Logged in!', 'success'); 51 | } 52 | }); 53 | } 54 | }); 55 | }); 56 | 57 | /* 58 | * Helpers 59 | */ 60 | 61 | Template.login.helpers({ 62 | example: function(){ 63 | // Code to run for helper function. 64 | } 65 | }); 66 | 67 | /* 68 | * Events 69 | */ 70 | 71 | Template.login.events({ 72 | 'submit form': function(e){ 73 | // Prevent form from submitting. 74 | e.preventDefault(); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /code/client/controllers/public/recover-password.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller: Recover Password 3 | * Template: /client/views/public/recover-password.html 4 | */ 5 | 6 | /* 7 | * Created 8 | */ 9 | 10 | Template.recoverPassword.onCreated(function(){ 11 | // Code to run when template is created goes here. 12 | }); 13 | 14 | /* 15 | * Rendered 16 | */ 17 | 18 | 19 | Template.recoverPassword.onRendered(function(){ 20 | $('#application-recover-password').validate({ 21 | rules: { 22 | emailAddress: { 23 | required: true, 24 | email: true 25 | } 26 | }, 27 | messages: { 28 | emailAddress: { 29 | required: "Please enter your email address to recover your password.", 30 | email: "Please enter a valid email address." 31 | } 32 | }, 33 | submitHandler: function(){ 34 | // Grab the user's email address. 35 | var email = $('[name="emailAddress"]').val(); 36 | 37 | // Call the send reset password email method. 38 | Accounts.forgotPassword({email: email}, function(error){ 39 | if(error){ 40 | Bert.alert(error.reason, 'danger'); 41 | } else { 42 | Bert.alert('Check your inbox for a reset link!', 'success'); 43 | } 44 | }); 45 | } 46 | }); 47 | }); 48 | 49 | /* 50 | * Helpers 51 | */ 52 | 53 | Template.recoverPassword.helpers({ 54 | example: function(){ 55 | // Code to run for helper function. 56 | } 57 | }); 58 | 59 | /* 60 | * Events 61 | */ 62 | 63 | Template.recoverPassword.events({ 64 | 'submit form': function(e){ 65 | // Prevent form from submitting. 66 | e.preventDefault(); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /code/client/controllers/public/reset-password.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller: Reset Password 3 | * Template: /client/views/public/reset-password.html 4 | */ 5 | 6 | /* 7 | * Created 8 | */ 9 | 10 | Template.resetPassword.onCreated(function(){ 11 | // Code to run when template is created goes here. 12 | }); 13 | 14 | /* 15 | * Rendered 16 | */ 17 | 18 | Template.resetPassword.onRendered(function(){ 19 | $('#application-reset-password').validate({ 20 | rules: { 21 | newPassword: { 22 | required: true, 23 | minlength: 6 24 | }, 25 | repeatNewPassword: { 26 | required: true, 27 | minlength: 6, 28 | equalTo: "[name='newPassword']" 29 | } 30 | }, 31 | messages: { 32 | newPassword: { 33 | required: "Please enter a new password.", 34 | minlength: "Please use at least six characters." 35 | }, 36 | repeatNewPassword: { 37 | required: "Please repeat your new password.", 38 | equalTo: "Your password do not match. Please try again." 39 | } 40 | }, 41 | submitHandler: function(){ 42 | // Grab the user's reset token and new password. 43 | var token = Session.get('resetPasswordToken'), 44 | password = $('[name="newPassword"]').val(); 45 | 46 | // Reset the user's password. 47 | Accounts.resetPassword(token, password, function(error){ 48 | if(error){ 49 | Bert.alert(error.reason, 'danger'); 50 | } else { 51 | Bert.alert('Password successfully reset!', 'success'); 52 | Session.set('resetPasswordToken', null); 53 | } 54 | }); 55 | } 56 | }); 57 | }); 58 | 59 | /* 60 | * Helpers 61 | */ 62 | 63 | Template.resetPassword.helpers({ 64 | example: function(){ 65 | // Code to run for helper function. 66 | } 67 | }); 68 | 69 | /* 70 | * Events 71 | */ 72 | 73 | Template.resetPassword.events({ 74 | 'submit form': function(e){ 75 | // Prevent form from submitting. 76 | e.preventDefault(); 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /code/client/controllers/public/signup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller: Signup 3 | * Template: /client/views/public/signup.html 4 | */ 5 | 6 | /* 7 | * Rendered 8 | */ 9 | 10 | Template.signup.onRendered(function(){ 11 | $('#application-signup').validate({ 12 | rules: { 13 | emailAddress: { 14 | required: true, 15 | email: true 16 | }, 17 | password: { 18 | required: true, 19 | minlength: 6 20 | } 21 | }, 22 | messages: { 23 | emailAddress: { 24 | required: "Please enter your email address to sign up.", 25 | email: "Please enter a valid email address." 26 | }, 27 | password: { 28 | required: "Please enter a password to sign up.", 29 | minlength: "Please use at least six characters." 30 | } 31 | }, 32 | submitHandler: function(){ 33 | // Grab the user's details. 34 | var user = { 35 | email: $('[name="emailAddress"]').val(), 36 | password: $('[name="password"]').val() 37 | }; 38 | 39 | // Create the user's account. 40 | Accounts.createUser({email: user.email, password: user.password}, function( error ){ 41 | if(error){ 42 | Bert.alert(error.reason, 'danger'); 43 | } else { 44 | var userId = Meteor.userId(); 45 | Bert.alert('Welcome!', 'success'); 46 | Meteor.call( "initApiKey", userId ); 47 | } 48 | }); 49 | } 50 | }); 51 | }); 52 | 53 | /* 54 | * Events 55 | */ 56 | 57 | Template.signup.events({ 58 | 'submit form': function(e){ 59 | // Prevent form from submitting. 60 | e.preventDefault(); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /code/client/helpers/helpers-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * UI Helpers 3 | * Define UI helpers for common template functionality. 4 | */ 5 | 6 | /* 7 | * Current Route 8 | * Return an active class if the currentRoute session variable name 9 | * (set in the appropriate file in /client/routes/) is equal to the name passed 10 | * to the helper in the template. 11 | */ 12 | 13 | UI.registerHelper('currentRoute', function(route){ 14 | return Session.equals('currentRoute', route) ? 'active' : ''; 15 | }); 16 | -------------------------------------------------------------------------------- /code/client/includes/_header.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /code/client/layouts/layout-default.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /code/client/routes/hooks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Route Hooks 3 | * Hook functions for managing user access to routes. 4 | */ 5 | 6 | /* 7 | * Define Hook Functions 8 | */ 9 | 10 | /* 11 | * Hook: Check if a User is Logged In 12 | * If a user is not logged in and attempts to go to an authenticated route, 13 | * re-route them to the login screen. 14 | */ 15 | 16 | checkUserLoggedIn = function(){ 17 | if( !Meteor.loggingIn() && !Meteor.user() ) { 18 | Router.go('/login'); 19 | } else { 20 | this.next(); 21 | } 22 | } 23 | 24 | /* 25 | * Hook: Check if a User Exists 26 | * If a user is logged in and attempts to go to a public route, re-route 27 | * them to the index path. 28 | */ 29 | 30 | userAuthenticated = function(){ 31 | if( !Meteor.loggingIn() && Meteor.user() ){ 32 | Router.go('/'); 33 | } else { 34 | this.next(); 35 | } 36 | } 37 | 38 | /* 39 | * Run Hooks 40 | */ 41 | 42 | Router.onBeforeAction(checkUserLoggedIn, { 43 | except: [ 44 | 'signup', 45 | 'login', 46 | 'recover-password', 47 | 'reset-password' 48 | ] 49 | }); 50 | 51 | Router.onBeforeAction(userAuthenticated, { 52 | only: [ 53 | 'signup', 54 | 'login', 55 | 'recover-password', 56 | 'reset-password' 57 | ] 58 | }); 59 | -------------------------------------------------------------------------------- /code/client/routes/routes-authenticated.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Routes: Authenticated 3 | * Routes that are only visible to authenticated users. 4 | */ 5 | 6 | Router.route('index', { 7 | path: '/', 8 | template: 'index' 9 | }); 10 | 11 | Router.route('apiKey', { 12 | path: '/api-key', 13 | template: 'apiKey' 14 | }); 15 | -------------------------------------------------------------------------------- /code/client/routes/routes-global.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Routes: Global 3 | * Global router configurations that apply to the entire application. 4 | */ 5 | 6 | Router.configure({ 7 | loadingTemplate: 'loading', 8 | notFoundTemplate: 'notFound', 9 | layoutTemplate: 'layoutDefault' 10 | }); 11 | -------------------------------------------------------------------------------- /code/client/routes/routes-public.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Routes: Public 3 | * Routes that are visible to all (public) users. 4 | */ 5 | 6 | Router.route('signup', { 7 | path: '/signup', 8 | template: 'signup', 9 | onBeforeAction: function(){ 10 | Session.set('currentRoute', 'signup'); 11 | this.next(); 12 | } 13 | }); 14 | 15 | Router.route('login', { 16 | path: '/login', 17 | template: 'login', 18 | onBeforeAction: function(){ 19 | Session.set('currentRoute', 'login'); 20 | this.next(); 21 | } 22 | }); 23 | 24 | Router.route('recover-password', { 25 | path: '/recover-password', 26 | template: 'recoverPassword', 27 | onBeforeAction: function(){ 28 | Session.set('currentRoute', 'recover-password'); 29 | this.next(); 30 | } 31 | }); 32 | 33 | Router.route('reset-password', { 34 | path: '/reset-password/:token', 35 | template: 'resetPassword', 36 | onBeforeAction: function() { 37 | Session.set('currentRoute', 'reset-password'); 38 | Session.set('resetPasswordToken', this.params.token); 39 | this.next(); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /code/client/stylesheets/sass/application.scss: -------------------------------------------------------------------------------- 1 | /* Globals */ 2 | @import "globals/extends"; 3 | 4 | /* Views */ 5 | @import "views/public/login"; 6 | -------------------------------------------------------------------------------- /code/client/stylesheets/sass/globals/_extends.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Clearfix 3 | via http://nicolasgallagher.com/micro-clearfix-hack 4 | */ 5 | %clearfix { 6 | *zoom: 1; 7 | 8 | &:before, 9 | &:after { 10 | display: table; 11 | content: ""; 12 | } 13 | 14 | &:after { 15 | clear: both; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/client/stylesheets/sass/views/public/_login.scss: -------------------------------------------------------------------------------- 1 | .login label { 2 | display: block; 3 | @extend %clearfix; 4 | } 5 | -------------------------------------------------------------------------------- /code/client/views/authenticated/api-key.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /code/client/views/authenticated/index.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /code/client/views/public/loading.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /code/client/views/public/login.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /code/client/views/public/not-found.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /code/client/views/public/recover-password.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /code/client/views/public/reset-password.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /code/client/views/public/signup.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /code/collections/api-keys.js: -------------------------------------------------------------------------------- 1 | APIKeys = new Meteor.Collection( 'api-keys' ); 2 | 3 | /* 4 | * Allow 5 | */ 6 | 7 | APIKeys.allow({ 8 | insert: function(){ 9 | // Disallow inserts on the client by default. 10 | return false; 11 | }, 12 | update: function(){ 13 | // Disallow updates on the client by default. 14 | return false; 15 | }, 16 | remove: function(){ 17 | // Disallow removes on the client by default. 18 | return false; 19 | } 20 | }); 21 | 22 | /* 23 | * Deny 24 | */ 25 | 26 | APIKeys.deny({ 27 | insert: function(){ 28 | // Deny inserts on the client by default. 29 | return true; 30 | }, 31 | update: function(){ 32 | // Deny updates on the client by default. 33 | return true; 34 | }, 35 | remove: function(){ 36 | // Deny removes on the client by default. 37 | return true; 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /code/collections/pizza.js: -------------------------------------------------------------------------------- 1 | Pizza = new Meteor.Collection( 'pizza' ); 2 | 3 | /* 4 | * Allow 5 | */ 6 | 7 | Pizza.allow({ 8 | insert: function(){ 9 | // Disallow inserts on the client by default. 10 | return false; 11 | }, 12 | update: function(){ 13 | // Disallow updates on the client by default. 14 | return false; 15 | }, 16 | remove: function(){ 17 | // Disallow removes on the client by default. 18 | return false; 19 | } 20 | }); 21 | 22 | /* 23 | * Deny 24 | */ 25 | 26 | Pizza.deny({ 27 | insert: function(){ 28 | // Deny inserts on the client by default. 29 | return true; 30 | }, 31 | update: function(){ 32 | // Deny updates on the client by default. 33 | return true; 34 | }, 35 | remove: function(){ 36 | // Deny removes on the client by default. 37 | return true; 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /code/collections/users.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Allow 3 | */ 4 | 5 | Meteor.users.allow({ 6 | insert: function(){ 7 | // Disallow user inserts on the client by default. 8 | return false; 9 | }, 10 | update: function(){ 11 | // Disallow user updates on the client by default. 12 | return false; 13 | }, 14 | remove: function(){ 15 | // Disallow user removes on the client by default. 16 | return false; 17 | } 18 | }); 19 | 20 | /* 21 | * Deny 22 | */ 23 | 24 | Meteor.users.deny({ 25 | insert: function(){ 26 | // Deny user inserts on the client by default. 27 | return true; 28 | }, 29 | update: function(){ 30 | // Deny user updates on the client by default. 31 | return true; 32 | }, 33 | remove: function(){ 34 | // Deny user removes on the client by default. 35 | return true; 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "1.0.0", 4 | "description": "Application description.", 5 | "scripts": { 6 | "start": "meteor --settings settings-development.json", 7 | "staging": "modulus env set METEOR_SETTINGS \"$(cat settings-development.json)\" -p 'Staging' && modulus deploy -f -p 'Staging'", 8 | "production": "modulus env set METEOR_SETTINGS \"$(cat settings-production.json)\" -p 'Production' && modulus deploy -f -p 'Production'" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /code/packages.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /code/packages/npm-container/index.js: -------------------------------------------------------------------------------- 1 | Meteor.npmRequire = function(moduleName) { // 85 2 | var module = Npm.require(moduleName); // 86 3 | return module; // 87 4 | }; // 88 5 | // 89 6 | Meteor.require = function(moduleName) { // 90 7 | console.warn('Meteor.require is deprecated. Please use Meteor.npmRequire instead!'); // 91 8 | return Meteor.npmRequire(moduleName); // 92 9 | }; // 93 -------------------------------------------------------------------------------- /code/packages/npm-container/package.js: -------------------------------------------------------------------------------- 1 | var path = Npm.require('path'); // 97 2 | var fs = Npm.require('fs'); // 98 3 | // 99 4 | Package.describe({ // 100 5 | summary: 'Contains all your npm dependencies', // 101 6 | version: '1.0.0', // 102 7 | name: 'npm-container' // 103 8 | }); // 104 9 | // 105 10 | var packagesJsonFile = path.resolve('./packages.json'); // 106 11 | try { // 107 12 | var fileContent = fs.readFileSync(packagesJsonFile); // 108 13 | var packages = JSON.parse(fileContent.toString()); // 109 14 | Npm.depends(packages); // 110 15 | } catch(ex) { // 111 16 | console.error('ERROR: packages.json parsing error [ ' + ex.message + ' ]'); // 112 17 | } // 113 18 | // 114 19 | Package.onUse(function(api) { // 115 20 | api.add_files(['index.js', '../../packages.json'], 'server'); // 116 21 | }); // 117 -------------------------------------------------------------------------------- /code/server/admin/startup-functions/_test-data.js: -------------------------------------------------------------------------------- 1 | PIZZAS = [ 2 | { 3 | "name": "Cheese Pizza", 4 | "crust": "Regular", 5 | "toppings": [ 6 | 'Mozzarella Cheese', 7 | 'Tomato Sauce' 8 | ] 9 | }, 10 | { 11 | "name": "Pepperoni Pizza", 12 | "crust": "Pan", 13 | "toppings": [ 14 | 'Mozzarella Cheese', 15 | 'Tomato Sauce', 16 | 'Pepperoni', 17 | 'Basil' 18 | ] 19 | }, 20 | { 21 | "name": "Margheritta Pizza", 22 | "crust": "Thin", 23 | "toppings": [ 24 | 'Fresh Mozzarella Cheese', 25 | 'Tomato Sauce', 26 | 'Basil' 27 | ] 28 | }, 29 | { 30 | "name": "Supreme Pizza", 31 | "crust": "Deep Dish", 32 | "toppings": [ 33 | 'Fresh Mozzarella Cheese', 34 | 'Tomato Sauce', 35 | 'Basil', 36 | 'Onions', 37 | 'Black Olives', 38 | 'Green Peppers', 39 | 'Italian Sausage', 40 | 'Pepperoni' 41 | ] 42 | } 43 | ]; 44 | -------------------------------------------------------------------------------- /code/server/admin/startup-functions/browser-policies.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Browser Policies 3 | * Browser policy customizations. 4 | * Documentation: https://atmospherejs.com/meteor/browser-policy 5 | */ 6 | 7 | customBrowserPolicies = function(){ 8 | // Font Awesome 9 | BrowserPolicy.content.allowOriginForAll('maxcdn.bootstrapcdn.com'); 10 | }; 11 | -------------------------------------------------------------------------------- /code/server/admin/startup-functions/test-accounts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate Test Accounts 3 | * Creates a collection of test accounts automatically on startup. 4 | */ 5 | 6 | generateTestAccounts = function(){ 7 | // Create an array of user accounts. 8 | var users = [ 9 | { name: "Admin", email: "admin@admin.com", password: "password" } 10 | ]; 11 | 12 | // Loop through array of user accounts. 13 | for(i=0; i < users.length; i++){ 14 | // Check if the user already exists in the DB. 15 | var userEmail = users[i].email, 16 | checkUser = Meteor.users.findOne({"emails.address": userEmail}); 17 | 18 | // If an existing user is not found, create the account. 19 | if ( !checkUser ) { 20 | var user = Accounts.createUser({ 21 | email: userEmail, 22 | password: users[i].password, 23 | profile: { 24 | name: users[i].name 25 | } 26 | }); 27 | 28 | Meteor.call( "initApiKey", user ); 29 | 30 | // Load our default user up with some pizzas so we have some data 31 | // to work with out of the box. 32 | for( var i = 0; i < PIZZAS.length; i++ ) { 33 | PIZZAS[ i ].owner = user; 34 | Pizza.insert( PIZZAS[ i ] ); 35 | } 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /code/server/admin/startup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Startup 3 | * Functions to run on server startup. Note: this file is for calling functions 4 | * only. Define functions in /server/admin/startup-functions. 5 | */ 6 | 7 | Meteor.startup(function(){ 8 | 9 | // Custom Browser Policies 10 | customBrowserPolicies(); 11 | 12 | // Generate Test Accounts 13 | generateTestAccounts(); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /code/server/api/config/api.js: -------------------------------------------------------------------------------- 1 | API = { 2 | authentication: function( apiKey ) { 3 | var getUser = APIKeys.findOne( { "key": apiKey }, { fields: { "owner": 1 } } ); 4 | if ( getUser ) { 5 | return getUser.owner; 6 | } else { 7 | return false; 8 | } 9 | }, 10 | connection: function( request ) { 11 | var getRequestContents = API.utility.getRequestContents( request ), 12 | apiKey = getRequestContents.api_key, 13 | validUser = API.authentication( apiKey ); 14 | 15 | if ( validUser ) { 16 | // Now that we've validated our user, we make sure to scrap their API key 17 | // from the data we received. Next, we return a new object containing our 18 | // user's ID along with the rest of the data they sent. 19 | delete getRequestContents.api_key; 20 | return { owner: validUser, data: getRequestContents }; 21 | } else { 22 | return { error: 401, message: "Invalid API key." }; 23 | } 24 | }, 25 | handleRequest: function( context, resource, method ) { 26 | var connection = API.connection( context.request ); 27 | if ( !connection.error ) { 28 | API.methods[ resource ][ method ]( context, connection ); 29 | } else { 30 | API.utility.response( context, 401, connection ); 31 | } 32 | }, 33 | methods: { 34 | pizza: { 35 | GET: function( context, connection ) { 36 | // Check to see if our request has any data. If it doesn't, we want to 37 | // return all pizzas for the owner. If it does, we want to search for 38 | // pizzas matching that query. 39 | var hasQuery = API.utility.hasData( connection.data ); 40 | 41 | if ( hasQuery ) { 42 | connection.data.owner = connection.owner; 43 | // Note: we're doing a very simple find on our data here. This means that 44 | // with something like our Toppings parameter, we're looking for the 45 | // exact array passed (not the individual items in the array). To do 46 | // that, you'd need to parse out the array items from connection.data 47 | // and use the Mongo $in operator like: 48 | // Pizza.find( { "toppings": { $in: connection.data.toppings } } ); 49 | var getPizzas = Pizza.find( connection.data ).fetch(); 50 | 51 | if ( getPizzas.length > 0 ) { 52 | // We found some pizzas, we can pass a 200 (success) and return the 53 | // found pizzas. 54 | API.utility.response( context, 200, getPizzas ); 55 | } else { 56 | // Bummer, we didn't find any pizzas. We can pass a 404 (not found) 57 | // and return an error message. 58 | API.utility.response( context, 404, { error: 404, message: "No pizzas found, dude." } ); 59 | } 60 | } else { 61 | // Our request didn't contain any params, so we'll just return all of 62 | // the pizzas we have for the owner associated with the passed API key. 63 | var getPizzas = Pizza.find( { "owner": connection.owner } ).fetch(); 64 | API.utility.response( context, 200, getPizzas ); 65 | } 66 | }, 67 | POST: function( context, connection ) { 68 | // Make sure that our request has data and that the data is valid. 69 | var hasData = API.utility.hasData( connection.data ), 70 | validData = API.utility.validate( connection.data, { "name": String, "crust": String, "toppings": [ String ] }); 71 | 72 | if ( hasData && validData ) { 73 | connection.data.owner = connection.owner; 74 | var pizza = Pizza.insert( connection.data ); 75 | API.utility.response( context, 200, { "_id": pizza, "message": "Pizza successfully created!" } ); 76 | } else { 77 | API.utility.response( context, 403, { error: 403, message: "POST calls must have a name, crust, and toppings passed in the request body in the correct formats." } ); 78 | } 79 | }, 80 | PUT: function( context, connection ) { 81 | var hasQuery = API.utility.hasData( connection.data ), 82 | validData = API.utility.validate( connection.data, Match.OneOf( 83 | { "_id": String, "name": String }, 84 | { "_id": String, "crust": String }, 85 | { "_id": String, "toppings": [ String ] }, 86 | { "_id": String, "name": String, "crust": String }, 87 | { "_id": String, "name": String, "toppings": [ String ] }, 88 | { "_id": String, "crust": String, "toppings": [ String ] }, 89 | { "_id": String, "name": String, "crust": String, "toppings": [ String ] } 90 | )); 91 | 92 | if ( hasQuery && validData ) { 93 | // Save the ID of the pizza we want to update and then sanatize our 94 | // data so that it only includes name, crust, and toppings parameters. 95 | var pizzaId = connection.data._id; 96 | delete connection.data._id; 97 | 98 | var getPizza = Pizza.findOne( { "_id": pizzaId }, { fields: { "_id": 1 } } ); 99 | 100 | if ( getPizza ) { 101 | Pizza.update( { "_id": pizzaId }, { $set: connection.data } ); 102 | API.utility.response( context, 200, { "message": "Pizza successfully updated!" } ); 103 | } else { 104 | API.utility.response( context, 404, { "message": "Can't update a non-existent pizza, homeslice." } ); 105 | } 106 | } else { 107 | API.utility.response( context, 403, { error: 403, message: "PUT calls must have a pizza ID and at least a name, crust, or toppings passed in the request body in the correct formats (String, String, Array)." } ); 108 | } 109 | }, 110 | DELETE: function( context, connection ) { 111 | var hasQuery = API.utility.hasData( connection.data ), 112 | validData = API.utility.validate( connection.data, { "_id": String } ); 113 | 114 | if ( hasQuery && validData ) { 115 | var pizzaId = connection.data._id; 116 | var getPizza = Pizza.findOne( { "_id": pizzaId }, { fields: { "_id": 1 } } ); 117 | 118 | if ( getPizza ) { 119 | Pizza.remove( { "_id": pizzaId } ); 120 | API.utility.response( context, 200, { "message": "Pizza removed!" } ); 121 | } else { 122 | API.utility.response( context, 404, { "message": "Can't delete a non-existent pizza, homeslice." } ); 123 | } 124 | } else { 125 | API.utility.response( context, 403, { error: 403, message: "DELETE calls must have an _id (and only an _id) in the request body in the correct format (String)." } ); 126 | } 127 | } 128 | } 129 | }, 130 | utility: { 131 | getRequestContents: function( request ) { 132 | switch( request.method ) { 133 | case "GET": 134 | return request.query; 135 | case "POST": 136 | case "PUT": 137 | case "DELETE": 138 | return request.body; 139 | } 140 | }, 141 | hasData: function( data ) { 142 | return Object.keys( data ).length > 0 ? true : false; 143 | }, 144 | response: function( context, statusCode, data ) { 145 | context.response.setHeader( 'Content-Type', 'application/json' ); 146 | context.response.statusCode = statusCode; 147 | context.response.end( JSON.stringify( data ) ); 148 | }, 149 | validate: function( data, pattern ) { 150 | return Match.test( data, pattern ); 151 | } 152 | } 153 | }; 154 | -------------------------------------------------------------------------------- /code/server/api/resources/pizza.js: -------------------------------------------------------------------------------- 1 | Router.route( '/api/v1/pizza', function() { 2 | // Two parts here. Oof. So, our friend CORS is fussy. In order to get our 3 | // request through, we need to do two things: let it know that the request 4 | // is allowed from the originating server AND, let it know what options it 5 | // is allowed to send with the request. 6 | 7 | // There are two types of requests happening: OPTIONS and the actual request. 8 | // An OPTIONS request is known as a "pre-flight" request. Before the actual 9 | // request is run, it will ask if it is allowed to make the request, AND, 10 | // if the data it's asking to pass over is allowed. 11 | 12 | // Setting Access-Control-Allow-Origin answers the first question, by saying 13 | // what domains requests are allowed to be made from (in this case * is equal 14 | // to saying "anywhere"). 15 | this.response.setHeader( 'Access-Control-Allow-Origin', '*' ); 16 | 17 | // Here, we check the request method to see if it's an OPTIONS request, or, 18 | // a pre-flight check. If it is, we pass along a list of allowed headers and 19 | // methods, followed by an end to that request (the pre-flight). Once this is 20 | // received by the requesting server, it will attempt to perform the actual 21 | // request (GET, POST, PUT, or DELETE). 22 | if ( this.request.method === "OPTIONS" ) { 23 | this.response.setHeader( 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept' ); 24 | this.response.setHeader( 'Access-Control-Allow-Methods', 'POST, PUT, GET, DELETE, OPTIONS' ); 25 | this.response.end( 'Set OPTIONS.' ); 26 | } else { 27 | // If we've already passed through the OPTIONS request, we go ahead and call 28 | // our actual HTTP method. 29 | API.handleRequest( this, 'pizza', this.request.method ); 30 | } 31 | 32 | }, { where: 'server' } ); 33 | -------------------------------------------------------------------------------- /code/server/email/templates/reset-password.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Reset Password Email Template 3 | * Override Meteor defaults when sending a reset password email. 4 | */ 5 | 6 | // Set name and from email. 7 | Accounts.emailTemplates.resetPassword.siteName = "Application Name"; 8 | Accounts.emailTemplates.resetPassword.from = "Application Admin Email "; 9 | 10 | // Set a subject for the reset password email. 11 | Accounts.emailTemplates.resetPassword.subject = function(user){ 12 | return "[Application Name] Reset Your Password"; 13 | } 14 | 15 | // Set the body of the reset password email. 16 | Accounts.emailTemplates.resetPassword.text = function(user, url){ 17 | var email = user.emails[0].address, 18 | removeHash = url.replace('#/', ''); 19 | return "A password reset has been requested for the account related to this address(" + email + "). To reset the password, visit the following link:\n\n" + removeHash + "\n\n If you did not request this reset, please ignore this email. If you feel something is wrong, please contact support: admin@application.com." 20 | } 21 | -------------------------------------------------------------------------------- /code/server/methods/insert/api-key.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Methods: Insert - API Key 3 | * Creates the users API key. 4 | */ 5 | 6 | Meteor.methods({ 7 | initApiKey: function( userId ) { 8 | check( userId, Match.OneOf( Meteor.userId(), String ) ); 9 | 10 | var newKey = Random.hexString( 32 ); 11 | 12 | try { 13 | var key = APIKeys.insert({ 14 | "owner": userId, 15 | "key": newKey 16 | }); 17 | return key; 18 | } catch( exception ) { 19 | return exception; 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /code/server/methods/read/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Methods: Read - Example 3 | * Example of a method used for reading from the database. 4 | */ 5 | 6 | Meteor.methods({ 7 | exampleReadMethod: function(argument){ 8 | // Check the argument. Assuming a String type here. 9 | check(argument, String); 10 | 11 | // Perform the read. 12 | var exampleItem = Example.findOne(argument); 13 | 14 | // If the read fails (no documents found), throw an error. 15 | if (!exampleItem) { 16 | throw new Meteor.Error(500, 'Error 500: Not Found', 'No documents found.'); 17 | } 18 | 19 | // Return either the result or the error. 20 | return exampleItem; 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /code/server/methods/remove/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Methods: Remove - Example 3 | * Example of a method used for removing a document from the database. 4 | */ 5 | 6 | Meteor.methods({ 7 | exampleRemoveMethod: function(argument){ 8 | // Check the argument. Assuming a String type here. 9 | check(argument, String); 10 | 11 | // Perform the remove. 12 | try { 13 | var exampleId = Example.remove(argument); 14 | return exampleId; 15 | } catch(exception) { 16 | // If an error occurs, return it to the client. 17 | return exception; 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /code/server/methods/update/api-key.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Methods: Update - API Key 3 | * Updates the users API key. 4 | */ 5 | 6 | Meteor.methods({ 7 | regenerateApiKey: function( userId ){ 8 | check( userId, Meteor.userId() ); 9 | 10 | var newKey = Random.hexString( 32 ); 11 | 12 | // Perform the update. 13 | try { 14 | var keyId = APIKeys.update( { "owner": userId }, { 15 | $set: { 16 | "key": newKey 17 | } 18 | }); 19 | return keyId; 20 | } catch(exception) { 21 | // If an error occurs, return it to the client. 22 | return exception; 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /code/server/methods/utility/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Methods: Utility - Example 3 | * Example of a method used for performing a function on the server. 4 | */ 5 | 6 | // Example third-party API stub to call. 7 | // This should be deleted and is only here as an example. 8 | var chipotle = { 9 | getBurrito: function(burrito){ 10 | return burrito; 11 | } 12 | } 13 | 14 | Meteor.methods({ 15 | exampleUtilityMethod: function(argument){ 16 | // Check the argument. Assuming an Object type here. 17 | check(argument, Object); 18 | 19 | // Perform the function. 20 | try { 21 | var apiCall = chipotle.getBurrito("Barbacoa"); 22 | return apiCall; 23 | } catch(exception) { 24 | // If an error occurs, return it to the client. 25 | return exception; 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /code/server/publications/api-key.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Publications: Example 3 | * Data publications for the Example collection. 4 | */ 5 | 6 | Meteor.publish( 'APIKey', function(){ 7 | var user = this.userId; 8 | var data = APIKeys.find( { "owner": user }, {fields: { "key": 1 } } ); 9 | 10 | if ( data ) { 11 | return data; 12 | } 13 | 14 | return this.ready(); 15 | }); 16 | -------------------------------------------------------------------------------- /code/settings-development.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "key": "value" 4 | }, 5 | "private": { 6 | "key": "value" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /code/smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "iron-router": {}, 4 | "sass": {}, 5 | "bootstrap-3": {}, 6 | "handlebars-server": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /code/smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": {}, 3 | "dependencies": { 4 | "basePackages": { 5 | "iron-router": {}, 6 | "sass": {}, 7 | "bootstrap-3": {}, 8 | "handlebars-server": {} 9 | }, 10 | "packages": { 11 | "iron-router": { 12 | "git": "https://github.com/EventedMind/iron-router.git", 13 | "tag": "v0.7.1", 14 | "commit": "d1ffb3f06ea4c112132b030f2eb1a70b81675ecb" 15 | }, 16 | "sass": { 17 | "git": "https://github.com/particle4dev/meteor-sass.git", 18 | "tag": "v0.1.7", 19 | "commit": "b41757f87475186b1485ec54a819d6e027ee3d88" 20 | }, 21 | "bootstrap-3": { 22 | "git": "https://github.com/mangasocial/meteor-bootstrap-3.git", 23 | "tag": "v3.2.0-1", 24 | "commit": "17a63902fc6cf1096c5289d963c66cf28b474727" 25 | }, 26 | "handlebars-server": { 27 | "git": "https://github.com/EventedMind/meteor-handlebars-server.git", 28 | "tag": "v1.2.0", 29 | "commit": "1b72c4e9d82af66293e50be4516c487d15d97464" 30 | }, 31 | "blaze-layout": { 32 | "git": "https://github.com/EventedMind/blaze-layout.git", 33 | "tag": "v0.2.4", 34 | "commit": "b40e9b0612329288d75cf52ad14a7da64bb8618f" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /writing-an-api.md: -------------------------------------------------------------------------------- 1 | ### Getting Started 2 | For this recipe, we're going to need to make sure we have access to the following packages. These will help us with creating our actual API but also securing data and authenticating requests _to_ that API. 3 | 4 |

Terminal

5 | 6 | ```bash 7 | meteor add iron:router 8 | ``` 9 | We'll use Iron Router's server side routes feature to create the URLs or "endpoints" that users will visit to interact with our API. 10 | 11 |

Terminal

12 | 13 | ```bash 14 | meteor add check 15 | ``` 16 | We'll use the `Match.test()` method given to us by the check package to validate request parameters that users send to us. 17 | 18 |

Terminal

19 | 20 | ```bash 21 | meteor add random 22 | ``` 23 | We'll use the Random package to help us generate our API keys that users will include to authenticate their requests. 24 | 25 |

Terminal

26 | 27 | ```bash 28 | meteor add http 29 | ``` 30 | We'll use the HTTP package to help us test out our API once we're all done. 31 | 32 |
33 |

Additional Packages

34 |

This recipe relies on several other packages that come as part of Base, the boilerplate kit used here on The Meteor Chef. The packages listed above are merely recipe-specific additions to the packages that are included by default in the kit. Make sure to reference the Packages Included list for Base to ensure you have fulfilled all of the dependencies.

35 |
36 | 37 | ### Terminology 38 | There are a handful of terms that will be used throughout this recipe that would be helpful to clarify now. If you're already familiar with APIs and the lingo that people use to talk about them, you can skip this part. If you're new to the scene, pull up a chair and we'll get you up to speed. 39 | 40 | #### API 41 | When it comes to software development, `API`—or, Application Programming Interface—is generally acknowledged as the methods, functions, or data that an application exposes for other software to use. This makes it possible for applications to work together without requiring direct collaboration between any two development teams. 42 | 43 | In the context of this recipe, `API` will refer to the data and functions developers can access in our application via URLs or endpoints. 44 | 45 | #### Endpoint 46 | An `endpoint` is the URL that developers use to gain access to data or functionality in our application. For example, `http://website.com/api/v1/pizzas/toppings.json` is an endpoint that will return data, while `http://website.com/api/v1/pizzas/update` is an endpoint that can access functionality to update a pizza. If this is confusing, don't worry, it will make sense when we implement our own API later on. 47 | 48 | #### Resource 49 | A `resource` is the actual _thing_ in our application that a user is consuming. Resources are accessed either in the form of groups (also referred to as collections), or on their own. For example, the endpoint `http://website.com/api/v1/pizzas` is meant to point us to the _pizzas_ resource, or, all of the data and functions related to pizzas. Conversely, the endpoint `http://website.com/api/v1/toppings` points us to the _toppings_ resource. 50 | 51 | #### Request 52 | A `request` is the term used to refer to the action performed by another application on our API. A request is made to an endpoint and usually contains some sort of data that we can use to _fulfill_ that request. For example, a user may request to create a new pizza in our application and in order to fulfill that request, we'd need to know the name of the pizza, what toppings it would have on it, and what type of crust it would have. 53 | 54 | #### Response 55 | A `response` is the term used to refer to our application responding to the request of another application. Depending on the type of request made by the other application our response will contain a status code—a three digit number that lets the other application know whether their request succeeded—and/or the data they requested, or a message confirming their action. 56 | 57 | #### HTTP Verbs 58 | HTTP verbs—or methods—are the different types of requests that a user can make on our API. The four types/verbs we'll focus on in this recipe are `GET`, `POST`, `PUT`, and `DELETE`. Each verb corresponds to a different type of action that our API can use to delegate the user's request to the right data or functionality that our API exposes. 59 | 60 | #### HTTP Status Code 61 | An `HTTP Status Code` is a three-digit number that an application can use to describe the result of a certain request. For example, if an action was successful an application might respond with `200` or `OK`. If a user made a request for some data that could not be found, an application would respond with `404` or `Not Found`. There are [several HTTP Status Codes](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) that applications can use to communicate the results of actions with one another. 62 | 63 | #### API Key 64 | An `API Key` is a unique identifier used by an API to _authenticate_ requests from users. It's generally presented in the form of a long, hexadecimal string (usually 32 characters or more). API Keys are randomly generated and can be issued (generated) or revoked (made invalid). 65 | 66 | ### An API for pizza 67 | To make our work a little easier to understand, we'll be creating an API that allows our users to do the following: 68 | 69 | - Get a pizza from our database by name, topping(s), or crust. 70 | - Create a new pizza in our database with a name, topping(s), and crust. 71 | - Update an existing pizza in our database with a name, topping(s), or crust. 72 | - Delete an existing pizza in our database. 73 | 74 | Pizza! To get us started off on the right foot, we're going to begin by focusing on issuing and revoking API tokens for our users. This will ensure that we're thinking about security from the start and not tacking it on later. 75 | 76 | ### Issuing API keys 77 | 78 | In order for users to access our API, we need some sort of authentication. When users login to our application's graphical interface, they just use a username and password. When they access our API, though, we'd like to use something that's a little more secure. 79 | 80 | An API key allows us to accomplish this because of a few properties: 81 | 82 | 1. It's a long, random, difficult to guess (unlike, potentially, a password) string of characters. 83 | 2. It can be used to reference a user's account ID without compromising their username and password. 84 | 3. It can be reset, meaning if a security breach _does_ occur, a user can invalidate an API key making it useless to anyone who uses it. 85 | 86 | For our API, we're going to issue API keys in two ways: once, when the user signs up for our application and then again, whenever they click the "regenerate key" button in their profile. 87 | 88 | Let's get started by taking a look at how we accomplish this when a new user signs up. 89 | 90 | #### Generating a key on signup 91 | 92 | When our user first signs up, they haven't had a chance to generate an API key so we need to do it on their behalf. To do this, we need to create a server side method that we can call _after_ the user's account has been created that can apply a key to their account by referencing their `userId`. Let's take a look. 93 | 94 |

/server/methods/insert/api-key.js

95 | 96 | ```javascript 97 | Meteor.methods({ 98 | initApiKey: function( userId ) { 99 | check( userId, Match.OneOf( Meteor.userId(), String ) ); 100 | 101 | var newKey = Random.hexString( 32 ); 102 | 103 | try { 104 | var key = APIKeys.insert({ 105 | "owner": userId, 106 | "key": newKey 107 | }); 108 | return key; 109 | } catch( exception ) { 110 | return exception; 111 | } 112 | } 113 | }); 114 | ``` 115 | 116 | Super simple, right? We start by using `check()` to verify that our `userId` is equal to the currently logged in user. We also make sure to check for a plain `String` value, too, as this could break when we auto-generate a user on startup (they exist in the DB but are not logged in). Next, we generate a new API key for them using the Random package's `hexString()` method. Note: here we pass the number `32` meaning we want our randomly generated key to be 32 characters in length. This number can be customized, so if you want more or less security, you can change it to fit your needs. 117 | 118 | Next, we `try` to insert a new key for our user into the `APIKeys` collection. Wait a minute! Where did this come from?! This collection was [setup beforehand](https://github.com/themeteorchef/writing-an-api/blob/master/code/collections/api-keys.js), but let's talk about _why_ we have a separate collection to begin with. The reason we want to separate our API key storage from the more predictable location of our user's `profile` (the writable portion of a user's record in the `Meteor.users()` collection) is that [by default, the `profile` object is _writable_](http://joshowens.me/the-curious-case-of-the-unknowing-leaky-meteor-security/). 119 | 120 | This means that without any extra considerations, were we to store our user's API key in their profile, _anyone_ with our user's ID could update the key (if our user documents were published to the client). Even though the code for this recipe has [already taken this into consideration](https://github.com/themeteorchef/base/blob/master/collections/users.js), it's still good to separate concerns a bit. Keep in mind, **this isn't bulletproof** and it's still recommended that your users practice good security by keeping their information safe from unwanted hands. 121 | 122 |
123 |

Educating in the UI

124 |

Because practicing good security is important, a good practice is educating users in your UI. Wherever they interact with their API key, make suggestions about how best to store and protect their key from unwanted eyes.

125 |
126 | 127 | #### Baking this into the signup flow 128 | 129 | Okay, so we have our method setup, but when and where do we actually call it? Let's take a look at the logic for our signup form (given to us as [part of Base](https://github.com/themeteorchef/base/blob/master/client/controllers/public/signup.js)). 130 | 131 |

/client/controllers/public/signup.js

132 | 133 | ```javascript 134 | Accounts.createUser({email: user.email, password: user.password}, function( error ){ 135 | if(error){ 136 | Bert.alert(error.reason, 'danger'); 137 | } else { 138 | var userId = Meteor.userId(); 139 | Bert.alert('Welcome!', 'success'); 140 | Meteor.call( "initApiKey", userId ); 141 | } 142 | }); 143 | ``` 144 | 145 | See the call? The last line in our `Accounts.createUser()` callback. We simply call our `initApiKey` method _after_ our user's account has been created (and they've been logged in—why we're using the call to `Meteor.userId()`). We're passing the user's ID to our method so we can pair the API key we generate to the user. Nice and simple! Now that we have this in place, let's take a look at how we display the key back to our user so they can use it in their HTTP requests as well as regenerate it. 146 | 147 | #### Key display and regeneration 148 | 149 | Okay! We have a key, but how do we see it and regenerate it? Let's take a look at our `apiKey` template first so we can understand how everything is structured. 150 | 151 |

/client/views/authenticated/api-key.html

152 | 153 | ```markup 154 | 167 | ``` 168 | The part we want to focus on is between the `
` near the bottom. Here, we have a disabled input field to display our key (this could be plain text, too) with a "cap" containing an icon to reset the API key. Note: the current API key is set as the value of the input. What we want to accomplish is getting our API key to display in the input and automatically update with a _new_ key when we click on the "cap" of the input. Let's see how to do it. 169 | 170 |

/client/controllers/authenticated/api-key.js

171 | 172 | ```javascript 173 | Template.apiKey.onCreated(function(){ 174 | this.subscribe( "APIKey" ); 175 | }); 176 | ``` 177 | 178 | First, we need a way to _see_ our API key. In order to do this, we can make use of Meteor's new [template-level subscriptions](https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe) to subscribe to our data. So it's clear, let's look at the publication this subscription is calling on real quick. 179 | 180 |

/server/publications/api-key.js

181 | 182 | ```javascript 183 | Meteor.publish( 'APIKey', function(){ 184 | var user = this.userId; 185 | var data = APIKeys.find( { "owner": user }, {fields: { "key": 1 } } ); 186 | 187 | if ( data ) { 188 | return data; 189 | } 190 | 191 | return this.ready(); 192 | }); 193 | ``` 194 | 195 | See the connection? Pretty simple. Of note, here we make it possible to find our current user's API key by using the handy `this.userId` value that Meteor gives us access to inside of publications. This way we don't have to pass the user's ID with our subscribe call. Nice! Next, we make sure to pass a projection to our `find`, returning only the `key` field (omitting the user's ID from the response). Every little bit of efficiency counts! Okay, let's see how we're piping the API key into the template. 196 | 197 |

/client/controllers/authenticated/api-key.js

198 | 199 | ```javascript 200 | Template.apiKey.helpers({ 201 | apiKey: function() { 202 | var apiKey = APIKeys.findOne(); 203 | 204 | if ( apiKey ) { 205 | return apiKey.key; 206 | } 207 | } 208 | }); 209 | ``` 210 | Straightforward enough. We simply do a `findOne()` and return the response (if it exists) to our `{{apiKey}}` helper. But wait...how are we doing this without a query or projections? Well, because we know that our publication is going to return only the document that matches our current user's ID _and_ only the `key` field, we can do a `findOne` because our template will only ever see _one document_. Make sense? No sense in writing extra code when the job is already finished! Now that getting our key into the template is square, let's check out regeneration. 211 | 212 |

/client/controllers/authenticated/api-key.js

213 | 214 | ```javascript 215 | Template.apiKey.events({ 216 | 'click .regenerate-api-key': function( ){ 217 | var userId = Meteor.userId(), 218 | confirmRegeneration = confirm( "Are you sure? This will invalidate your current key!" ); 219 | 220 | if ( confirmRegeneration ) { 221 | Meteor.call( "regenerateApiKey", userId, function( error, response ) { 222 | if ( error ) { 223 | Bert.alert( error.reason, "danger" ); 224 | } else { 225 | Bert.alert( "All done! You have a new API key.", "success" ); 226 | } 227 | }); 228 | } 229 | } 230 | }); 231 | ``` 232 | Two steps here. Because the regeneration action is _destructive_ meaning once this button is pushed the current API key is completely overwritten, we need to ask the user if they're sure. So, who cares? Well, as we'll learn in a bit, only the currently set API key is active. Again, this is a security measure so that if a key is leaked, our user can generate a new one effectively invalidating the old one. This means that if someone were to try and make an HTTP request on our API using that old key, they'd get an error! We are _so considerate_ of our users. 233 | 234 | ![David Mitchell pointing](https://media.giphy.com/media/n988gduPMFC8w/giphy.gif) 235 | 236 | Alright! Let's hop over to the server and take a look at how this is working. 237 | 238 |

/server/methods/update/api-key.js

239 | 240 | ```javascript 241 | Meteor.methods({ 242 | regenerateApiKey: function( userId ){ 243 | check( userId, Meteor.userId() ); 244 | 245 | var newKey = Random.hexString( 32 ); 246 | 247 | try { 248 | var keyId = APIKeys.update( { "owner": userId }, { 249 | $set: { 250 | "key": newKey 251 | } 252 | }); 253 | return keyId; 254 | } catch(exception) { 255 | return exception; 256 | } 257 | } 258 | }); 259 | ``` 260 | 261 | Almost identical to our `initApiKey` method from our signup flow, but with one small difference. Because we want to update an existing API key, we first want to look up the key in the database by the user's ID and _then_ set the key. Cool! With this in place, our user can click and confirm the regeneration on the client and get a fresh key in the input field we set up. Party! 262 | 263 | Things are looking great! We're ready to rock on implementing our actual API. We'll start by setting up the structure of our API so working on it is a little easier. 264 | 265 | ### Setting up our API 266 | 267 | _How_ we structure our API is just as important as the functionality that API provides. Technically speaking, our API is just another interface into our application, albeit not visual. Just like a graphical interface, though, we need to be considerate of how our API is organized because: 268 | 269 | 1. It makes it easier for our users to interact with. 270 | 2. It makes it easier for _us_ to maintain and expand. 271 | 272 | With that, said, let's look at our folder structure real quick. 273 | 274 | ```bash 275 | /server 276 | --- /api 277 | ------ /config 278 | --------- api.js 279 | ------ /resources 280 | --------- pizza.js 281 | ``` 282 | 283 | What's going on here? First, we treat our API almost like a separate application. Even though it's stored in the `/server` directory of our app, all of the code related to the API is sectioned off in its own directory. This is mostly for clarity and helps us to separate concerns around what code is responsible for what actions. Second, we break up the API into its different pieces. 284 | 285 | For this recipe, we have two parts: configuration and resources. Configuration—and more specifically our `api.js` file—is where we'll store a single object containing all of the methods and functions our API needs to function. Resources, on the other hand, are where we keep the available actions for each type of data. We'll only showcase one resource in this recipe, but a real API is likely to have multiple resources so its good to demonstrate the separation. 286 | 287 | #### A single API object 288 | 289 | To keep things simple and act as an aid to help us keep our code [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), we've defined a single object—set to the global variable `API`—in `/api/config/api.js` containing all of the code for our API. Let's take a look at the skeleton of the API to see what we mean. 290 | 291 |

/server/api/config/api.js

292 | 293 | ```javascript 294 | API = { 295 | authentication: function( apiKey ) {}, 296 | connection: function( request ) {}, 297 | handleRequest: function( context, resource, method ) {}, 298 | methods: { 299 | pizza: { 300 | GET: function( context, connection ) {}, 301 | POST: function( context, connection ) {}, 302 | PUT: function( context, connection ) {}, 303 | DELETE: function( context, connection ) {} 304 | } 305 | }, 306 | resources: {}, 307 | utility: { 308 | getRequestContents: function( request ) {}, 309 | hasData: function( data ) {}, 310 | response: function( context, statusCode, data ) {}, 311 | validate: function( data, pattern ) {} 312 | } 313 | }; 314 | ``` 315 | Wow! This is a lot. Don't worry, we'll step through each piece so it makes sense. All we want to point out here is that we're consolidating everything in our API into one place. Again, this is just an organizational technique to slim down our code and make it easier to reason about what functionality we have access to. As we build out each of the objects and functions inside of our `API` object, you will start to see why this structure is convenient. 316 | 317 | Let's get started by defining our resources. 318 | 319 | ### Defining our resources 320 | 321 | For this recipe, we actually only have on resource: pizza. Recall that a resource is simply the thing in our application that a user is consuming. In order for them to consume it, we need to make that resource accessible at an _endpoint_. Again, an endpoint is simply a fancy name for a URL (or URI depending on [which way your door swings](https://danielmiessler.com/study/url_vs_uri/)). 322 | 323 | To do this, we're going to use Iron Router's server side routing feature. First, let's look at how our route is defined and then dive into how we use it. 324 | 325 |

/server/api/resources/pizza.js

326 | 327 | ```javascript 328 | Router.route( '/api/v1/pizza', function() { 329 | // This is where we handle the request. 330 | }, { where: 'server' } ); 331 | ``` 332 | 333 | Pretty simple, right? If you've worked with Iron Router before, this should look familiar. We're doing three things here: 334 | 335 | 1. Passing a `path` parameter to let Iron Router know what URL (relative to our application's domain) our resource will be accessible at. 336 | 2. A callback function that will be called whenever our route is visited. 337 | 3. An `options` parameter with a `where` setting set to `'server'`. 338 | 339 | When we define a route like this on the server, we end up giving this route access to Node's `request` and `response` methods. These are what we'll use to communicate with HTTP requests. Again, the `request` being what we _receive_ and `response` being what we _send back_. Let's look at how we're actually using this for our own API. 340 | 341 |
342 |

API Versioning

343 |

You may have noticed that the URL for our endpoints is prefixed with /api/v1. What's that? Just like a piece of software, we want to version our API so that consumers of our API know what functionality they have access to with each iteration.

For example, we might want to change an endpoint's URL but we don't want to break the existing version. What we can do, then, is create a new version of our API, prefixing all new URLs with the new version. Because this is the first iteration of our API, we prefix all of our URLs with /api/v1/. In the future, we'll keep all of our /api/v1/ urls accessible, while still allowing us to expand our API by changing out the version number.

344 |

This is honestly a bit heady and confusing at first. I highly recommend checking out this talk by Amber Feng, the Product Engineering Lead at Stripe. In it she discusses some of the design principles behind their API. It's well worth the half hour watch if you want to start thinking seriously about the design of your API. Food for thought!

345 |
346 | 347 |

/server/api/resources/pizza.js

348 | 349 | ```javascript 350 | Router.route( '/api/v1/pizza', function() { 351 | this.response.setHeader( 'Access-Control-Allow-Origin', '*' ); 352 | 353 | if ( this.request.method === "OPTIONS" ) { 354 | this.response.setHeader( 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept' ); 355 | this.response.setHeader( 'Access-Control-Allow-Methods', 'POST, PUT, GET, DELETE, OPTIONS' ); 356 | this.response.end( 'Set OPTIONS.' ); 357 | } else { 358 | API.handleRequest( this, 'pizza', this.request.method ); 359 | } 360 | 361 | }, { where: 'server' } ); 362 | ``` 363 | 364 | What in the blue blazes is _this_? Welcome, friend, to the wild west that is handling HTTP requests. This isn't as scary as it looks, but it is important to pay attention. Otherwise prepare to sink countless hours into fighting with CORS. 365 | 366 | What is CORS, you ask? [CORS (Cross Origin Resource Sharing)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) is a standard used by browser makers to handle secure communication between web servers using HTTP. In theory, CORS is great. It should allow us to easily define who is allowed to communicate with our application and _what_ they're allowed to share with us. If only it were that easy. 367 | 368 | #### Pre-flight OPTIONS requests 369 | 370 | When an HTTP request is made to our server from an origin (domain) other than the one where the app is living, CORS will perform a process known as "pre-flight." This means that before an actual request is processed, CORS will call to the server being requested to make sure that it's allowed to send the request as well as what it's allowed to send with it. This is like that super paranoid parent calling ahead to the pool party to make sure a life guard will be on duty in case Little Jimmy gets tangled up in one of his pool noodles. _Sigh_. 371 | 372 | What you see in the example above is how we need to negotiate this `OPTIONS` request. First, we "globally" (meaning for any type of HTTP request, `OPTIONS` or otherwise) set something called `Access-Control-Allow-Origin`. This rule says from which domains a request is allowed to come from. Here, we've set this to `*` (an asterisk) to say "any domain can send us a request." Keep in mind, you may not always want to do this. If your API requires significant security, invest some time into researching this setting so that you only allow the domains you really want making requests. 373 | 374 | Next, we test our request's method to see if it _is_ an `OPTIONS` method and if its, we do three things: 375 | 376 | 1. Set the `Access-Control-Allow-Headers` parameter to let our request know which HTTP headers are safe to send with the request. 377 | 2. Set the `Access-Control-Allow-Methods` parameter to let our request know which type of HTTP requests we allow. 378 | 3. End the response to the `OPTIONS` request passing an arbitrary message. 379 | 380 | What this accomplishes is a phone call back to Little Jimmy's parents saying "yes, a life guard will be on duty," or, "here is how you're allowed to communicate with this server." Make sense? 381 | 382 |
383 |

CORS is a pain

384 |

While I've simplified it down quite a bit here, don't let it fool you: CORS is a jerk. It took quite a bit of time to find the solution you see above. This should cover you for most cases, but fair warning, CORS will drive you insane depending on what you want to accomplish.

385 |
386 | 387 | Now that CORS is handled, we can dig into _actually_ handling our request. 388 | 389 | ### Handling requests 390 | Okay. We've got our endpoint all ready to go so now we need to start talking about handling requests. Remember, we're going to focus on handling the four most popular types of requests: `GET`, `POST`, `PUT`, and `DELETE`. This is where we're going to put on our engineering cap, so pay close attention. Our goal is to bake all of our functionality into our `API` object in such a way that we can reuse pieces and avoid duplication. Let's dig in! 391 | 392 | #### handleRequest 393 | 394 | Because all of our requests will need to perform some common tasks that are not specific to the _type_ of request, we want to create a function that acts as a common starting point for _all_ of our requests. We're going to do something a bit funky and show how we're going to _call_ this common function before we actually define it. 395 | 396 |

/server/api/resources/pizza.js

397 | 398 | ```javascript 399 | API.handleRequest( this, 'pizza', this.request.method ); 400 | ``` 401 | 402 | Woah buddy. What the heck is going on here? Each of our requests will need to perform some common tasks like authentication and getting data _out of the request_. The `handleRequest` function that we're going to define below will be responsible for taking in arguments specific to the type of request and then _delegating_ those to the right methods. Let that sink in. Think of `handleRequst` like the post office clerk sorting mail. It will put the right letters (requests) into the right mailboxes (methods). Stick with me, this will start to make sense in a bit. 403 | 404 | Here, we're passing three arguments: `context`, `resource`, and `method`. `context` being passed as `this` is referring to the context within which `handleRequest` is being called, or, _the current request_ being made to the API. `resource` is simply referring to the resource we want to interact with and `method` is the type of method we're trying to perform. Let's hop over to the definition for `handleRequest` to see how this is wired up. 405 | 406 |

/server/api/config/api.js

407 | 408 | ```javascript 409 | API = { 410 | handleRequest: function( context, resource, method ) { 411 | var connection = API.connection( context.request ); 412 | if ( !connection.error ) { 413 | API.methods[ resource ][ method ]( context, connection ); 414 | } else { 415 | API.utility.response( context, 401, connection ); 416 | } 417 | } 418 | }; 419 | ``` 420 | Whaaaaat is this?! Don't panic. Again, we're just delegating here. Because there are a handful of steps to perform _before_ we get to interacting with the API, we want to divvy up each task to _a single function_. This helps not only with reusability, but makes it easier to come back to our code later and understand how information is flowing. We'll look at this line by line. Get ready for a bit of Inception. 421 | 422 | #### API.connection 423 | When a user "connects" to our API, we need to do two things: authenticate their access and pull the relevant data out of their request. Let's take a look. 424 | 425 |

/server/api/config/api.js

426 | 427 | ```javascript 428 | API = { 429 | connection: function( request ) { 430 | var getRequestContents = API.utility.getRequestContents( request ), 431 | apiKey = getRequestContents.api_key, 432 | validUser = API.authentication( apiKey ); 433 | 434 | if ( validUser ) { 435 | delete getRequestContents.api_key; 436 | return { owner: validUser, data: getRequestContents }; 437 | } else { 438 | return { error: 401, message: "Invalid API key." }; 439 | } 440 | }, 441 | }; 442 | ``` 443 | 444 | Hang in there. This first step, `connection` is responsible for pulling apart our request to get some information. First, we need to get our user's API key. In order to authenticate each of their requests, we ask that our users pass their API key as part of either the `query` parameters in their request or, as part of the `body` in their request. We'll see how our user sets these later. For now, just know that we ask our users to store their API key in either the `query` or `body` object as a parameter labeled `api_key`. 445 | 446 | To get the actual `api_key` value, we first use a method called `getRequestContents`. This method takes our `request` object and returns the correct object, `query` or `body`, depending on the type of request. That may be a little confusing, let's take a look. 447 | 448 | #### utility.getRequestContents 449 | 450 |

/server/api/config/api.js

451 | 452 | ```javascript 453 | API = { 454 | utility: { 455 | getRequestContents: function( request ) { 456 | switch( request.method ) { 457 | case "GET": 458 | return request.query; 459 | case "POST": 460 | case "PUT": 461 | case "DELETE": 462 | return request.body; 463 | } 464 | } 465 | } 466 | }; 467 | ``` 468 | 469 | See what's happening here? We're using a JavaScript switch method to look at what type of method is being called ( `GET`, `POST`, `PUT`, or `DELETE`), returning the object where we expect our data to be passed. `GET` requests are expected to pass data in the `query` object, while `POST`, `PUT`, and `DELETE` are expected to pass data in the `body` object. This just keeps our code a little cleaner! Awesome. 470 | 471 |

/server/api/config/api.js

472 | 473 | ```javascript 474 | API = { 475 | connection: function( request ) { 476 | var getRequestContents = API.utility.getRequestContents( request ), 477 | apiKey = getRequestContents.api_key, 478 | validUser = API.authentication( apiKey ); 479 | [...] 480 | }, 481 | }; 482 | ``` 483 | 484 | Back in our `connection` method, once we have the `query` or `body` object, we can pull out our API key. Let's take a look at how we authenticate requests so this all makes sense. 485 | 486 | #### Authenticating requests 487 | 488 | Our authentication process is pretty simple. We want to do two things: 489 | 1. Make sure the API key we've received actually _exists_. 490 | 2. Get the `owner` field for that key (the user's ID). 491 | 492 | ```javascript 493 | API = { 494 | authentication: function( apiKey ) { 495 | var getUser = APIKeys.findOne( { "key": apiKey }, { fields: { "owner": 1 } } ); 496 | if ( getUser ) { 497 | return getUser.owner; 498 | } else { 499 | return false; 500 | } 501 | } 502 | } 503 | ``` 504 | 505 | Pretty simple, right? We do a `findOne` on our `APIKeys` collection for the key passed by the user. We pass a `fields` projection to retrieve just the `owner` field (our user's ID). If we get a user (meaning our `findOne` doesn't return as `undefined` but as an object), we return the `owner` field. If we don't find a matching API key, we return false. Let's jump back up a level and see how this works. 506 | 507 |

/server/api/config/api.js

508 | 509 | ```javascript 510 | API = { 511 | connection: function( request ) { 512 | var getRequestContents = API.utility.getRequestContents( request ), 513 | apiKey = getRequestContents.api_key, 514 | validUser = API.authentication( apiKey ); 515 | 516 | if ( validUser ) { 517 | delete getRequestContents.api_key; 518 | return { owner: validUser, data: getRequestContents }; 519 | } else { 520 | return { error: 401, message: "Invalid API key." }; 521 | } 522 | }, 523 | }; 524 | ``` 525 | See how we're assigning the result of `API.authentication()` to the `validUser` variable? This is allowing us to halt the request and return an error if the API key we've received is invalid. Note that here, we're simply returning an object with an `error` parameter containing an HTTP status code and a `message` parameter to explain what went wrong. We'll look at how this is utilized in a bit. Next, let's look at what happens when an API key _is valid_. 526 | 527 |
528 |

Take a Break

529 |

Woof! This is a lot, I know, but the payoff is worth it. Let's take five and grab a snack.

530 |
531 | 532 |

/server/api/config/api.js

533 | 534 | ```javascript 535 | API = { 536 | connection: function( request ) { 537 | [...] 538 | 539 | if ( validUser ) { 540 | delete getRequestContents.api_key; 541 | return { owner: validUser, data: getRequestContents }; 542 | } else { 543 | return { error: 401, message: "Invalid API key." }; 544 | } 545 | }, 546 | }; 547 | ``` 548 | 549 | Last step for our connection! With the response of `utility.getRequestContents()` stored in our `getRequestContents` variable, we create a new object to return to the client. Here, we assign two parameters: `owner`, equal to our user's ID obtained during our authentication step, and `data`, the data we just retrieved from the request. Boom! Note: before we assign `getRequestContents` to the `data` parameter, we remove the `api_key` value from it since it's no longer needed. 550 | 551 | Let's jump back up to our `handleRequest()` call to see how this all plays out. 552 | 553 |

/server/api/config/api.js

554 | 555 | ```javascript 556 | API = { 557 | handleRequest: function( context, resource, method ) { 558 | var connection = API.connection( context.request ); 559 | if ( !connection.error ) { 560 | API.methods[ resource ][ method ]( context, connection ); 561 | } else { 562 | API.utility.response( context, 401, connection ); 563 | } 564 | } 565 | }; 566 | ``` 567 | 568 | Okay, back to the top! For now! This should be starting to make some sense. We're taking the result of all that work from our `connection()` method and using it to...do more delegation! Argh! Good god man, are you trying to drive us insane?! No. Not at all. 569 | 570 | ![Ron Burgandy saying I don't believe you](http://media.giphy.com/media/UTm86phGUMMQE/giphy.gif) 571 | 572 | Okay, okay. Back to Adult Town. Let's look at how we're handling that error we returned earlier if a user's API key was bogus. See that `API.utility.response()` thingamajig? That's our next wormhole. Let's jump in. 573 | 574 | #### utility.response() 575 | 576 | When we're working with HTTP requests, we need to acknowledge them somehow. Just like our user can send us a _request_, we can send them a _response_. It's easy to repeat a lot of code doing this, so again, we've simplified this into a reusable function so we can flex our geek muscles. Shall we? 577 | 578 |

/server/api/config/api.js

579 | 580 | ```javascript 581 | API = { 582 | utility: { 583 | response: function( context, statusCode, data ) { 584 | context.response.setHeader( 'Content-Type', 'application/json' ); 585 | context.response.statusCode = statusCode; 586 | context.response.end( JSON.stringify( data ) ); 587 | } 588 | } 589 | }; 590 | ``` 591 | 592 | This is pretty straightforward. We start by setting a header `Content-Type` equal to `application/json`. This lets the requesting server know what type of data we intend to send back. To keep things simple we'll be sending back `JSON` data. This can be a lot of different things, so make sure to set it depending on the _actual_ data your API is responding with. 593 | 594 | Once that is set, we need to respond with an HTTP status code. Remember, this is the three digit number that servers use to refer to certain events. We're getting this, here, as an argument to our `utility.response()` method. When we set this, we're telling the requesting server the result of their request. 595 | 596 | _Finally_, we're ending our response to the request, passing our data. Here, we use `JSON.stringify()` because... 597 | 598 |
599 |

The JSON.stringify() method converts a JavaScript value to a JSON string.

600 | Mozilla Developer Network 601 |
602 | 603 | This means that we can safely transmit our data in our response as `JSON` data. Note: we're doing this because our request is expecting `JSON` data to be returned. Why? Because that's what we told it we're responding with a few lines earlier when we set `context.response.setHeader( 'Content-Type', 'application/json' )`! 604 | 605 | That's it for our `utility.response()` method. Let's jump back up and take a look at how we're (finally) handling the response. 606 | 607 | ### Handling responses 608 | 609 | Okay, this is where the rubber meets the road. When we say "handling responses" what we really mean is fulfilling a request. At this point, we've authenticated our users access to our API and grabbed the data they've lobbed over to us. Now, we want to take a look at what _resource_ the request wants to work with and what _method_ it wants to use. Back in our `handleRequest` method, let's look at how we're making this work... 610 | 611 |

/server/api/config/api.js

612 | 613 | ```javascript 614 | API = { 615 | handleRequest: function( context, resource, method ) { 616 | var connection = API.connection( context.request ); 617 | if ( !connection.error ) { 618 | API.methods[ resource ][ method ]( context, connection ); 619 | } else { 620 | API.utility.response( context, 401, connection ); 621 | } 622 | } 623 | }; 624 | ``` 625 | 626 | See that `API.methods[ resource ][ method ]( context, connection );` part? This is taking the resource and method passed to `handleRequest` and pointing it to the corresponding method inside of our `API` object. Notice that, too, we're sending along the context (`this` from our request) and the connection information we've received (the userId associated with the API key we received and the data we pulled out of their request). Let's jump into our `API` object and see how our methods are organized and then step through each to see what they do. 627 | 628 |

/server/api/config/api.js

629 | 630 | ```javascript 631 | API = { 632 | methods: { 633 | pizza: { 634 | GET: function( context, connection ) {}, 635 | POST: function( context, connection ) {}, 636 | PUT: function( context, connection ) {}, 637 | DELETE: function( context, connection ) {} 638 | } 639 | }, 640 | }; 641 | ``` 642 | See the mapping here? In our `handleRequest` method, we were doing `API.methods[ resource ][ method ]( context, connection )` which is like saying `API.methods.pizza.GET( context, connection );` We use bracket notation to allow for variable object/method names. If we had another resource called `tacos` and we wanted to call its `put` method, we'd get something like `API.methods.tacos.PUT( context, connection );`. Make sense? 643 | 644 | Because our resource `pizza` supports `GET`, `POST`, `PUT`, and `DELETE` methods, we've defined a function for each HTTP method that will be called when we receive that type of request. Handy! Now for the fun part: making our methods do something. 645 | 646 | #### GET Methods 647 | 648 | `GET` methods are used to retrieve data. They're best thought of as performing a search on another application. We're saying "hey, application, can you give me the pizza that matches the following parameters?" Cool, huh? Let's take a look at the method we've defined in our `API` object at `API.resources.pizza.get` to see how this all works. 649 | 650 |

/server/api/config/api.js

651 | 652 | ```javascript 653 | API = { 654 | methods: { 655 | pizza: { 656 | GET: function( context, connection ) { 657 | var hasQuery = API.utility.hasData( connection.data ); 658 | 659 | if ( hasQuery ) { 660 | connection.data.owner = connection.owner; 661 | var getPizzas = Pizza.find( connection.data ).fetch(); 662 | 663 | if ( getPizzas.length > 0 ) { 664 | API.utility.response( context, 200, getPizzas ); 665 | } else { 666 | API.utility.response( context, 404, { error: 404, message: "No pizzas found, dude." } ); 667 | } 668 | } else { 669 | var getPizzas = Pizza.find( { "owner": connection.owner } ).fetch(); 670 | API.utility.response( context, 200, getPizzas ); 671 | } 672 | } 673 | } 674 | }, 675 | }; 676 | ``` 677 | 678 | Some new stuff, some familiar stuff. Let's step through it. First, we introduce a new utility method `hasData`. This method is designed to help us figure out whether or not our user's request has any data associated with it. Why do we care? Well, we need to know what type of response to give. In a `GET` request, we have two outcomes: returning a specific piece of data, or, returning a collection of data. 679 | 680 | The difference here is that if our user has passed some parameters (a.k.a query parameters) with their `GET` request, we know that we want to look for a specific document (or subset of documents) that matches that query. If they _don't_ give us any parameters, we just want to return everything. There's one catch! Remember how our `authentication` method earlier gave us back the user ID? We want to use that here so that we're only getting the documents "owned" by _that user_. 681 | 682 | Before we get too far ahead of ourselves, let's pull apart that `hasData` method. 683 | 684 |

/server/api/config/api.js

685 | 686 | ```javascript 687 | API = { 688 | utility: { 689 | hasData: function( data ) { 690 | return Object.keys( data ).length > 0 ? true : false; 691 | } 692 | }; 693 | ``` 694 | Easy peasy! But super important. What we're doing here is taking the `data` parameter from our connection object (remember, this is where we stored the data we pulled from the request) and checking whether or not it has any keys (parameters). If it _does_ we return true. If not, we slap a `false` on it. Good? Clear? Kosher? Back up! 695 | 696 |

/server/api/config/api.js

697 | 698 | ```javascript 699 | API = { 700 | methods: { 701 | pizza: { 702 | GET: function( context, connection ) { 703 | var hasQuery = API.utility.hasData( connection.data ); 704 | 705 | if ( hasQuery ) { 706 | connection.data.owner = connection.owner; 707 | var getPizzas = Pizza.find( connection.data ).fetch(); 708 | 709 | if ( getPizzas.length > 0 ) { 710 | API.utility.response( context, 200, getPizzas ); 711 | } else { 712 | API.utility.response( context, 404, { error: 404, message: "No pizzas found, dude." } ); 713 | } 714 | } else { 715 | var getPizzas = Pizza.find( { "owner": connection.owner } ).fetch(); 716 | API.utility.response( context, 200, getPizzas ); 717 | } 718 | } 719 | } 720 | }, 721 | }; 722 | ``` 723 | 724 | See how we're playing this fiddle? If we _do_ have data, we run a `find` on our `Pizza` collection. Next, we `fetch()` the result of that query (turn it into an array) and then evaluate whether or not it has any items (meaning it found something). If it _did_, we use our handy dandy `response` method we setup earlier to return a `200` (success) status code and then pass our found array of pizzas. Recall that from here, our `response` method will convert that array of objects into a JSON string using `JSON.stringify`. 725 | 726 | Sing it with me, kids: R-E-U-S-A-B-I-L-I-T-Y. 727 | 728 | Conversely, here, we also account for us finding _no_ pizzas matching the query. If we get bupkis, we return a 404 (not found) along with a message to let the requester know their query turned up with nothing. 729 | 730 | Just a ways down, we also handle what happens when we don't have any query parameters. See it? Instead of passing those parameters to our `find`, we instead just pass the `owner` (user ID). What happens here? Well, if there are _any_ pizzas in the `Pizza` collection where the owner is equal to our user's ID, we'll get them back! All of them! Pizza! 731 | 732 |
733 |

A working API

734 |

It may not seem like much, but with this method written we have a WORKING API. HOLY COW. Give yourself a pat on the back, this is huge!

735 |
736 | 737 | Okay, ready to keep swinging? Next up is handling `POST` methods. The good news? We're going to reuse a lot of code from here on out so the next three methods will go quick. 738 | 739 | #### POST Methods 740 | 741 | `POST` methods are used to insert or _create_ data. Let's take a look. 742 | 743 |

/server/api/config/api.js

744 | 745 | ```javascript 746 | API = { 747 | methods: { 748 | pizza: { 749 | POST: function( context, connection ) { 750 | var hasData = API.utility.hasData( connection.data ), 751 | validData = API.utility.validate( connection.data, { "name": String, "crust": String, "toppings": [ String ] }); 752 | 753 | if ( hasData && validData ) { 754 | connection.data.owner = connection.owner; 755 | var pizza = Pizza.insert( connection.data ); 756 | API.utility.response( context, 200, { "_id": pizza, "message": "Pizza successfully created!" } ); 757 | } else { 758 | API.utility.response( context, 403, { error: 403, message: "POST calls must have a name, crust, and toppings passed in the request body in the correct formats." } ); 759 | } 760 | }, 761 | } 762 | }, 763 | }; 764 | ``` 765 | 766 | This should be starting to make a lot of sense! A few things to call out. First, we've added _yet another method_. I know, I know. 767 | 768 | ![Neil DeGrasse Tyson...whatever](http://media.giphy.com/media/zGZOcFgBDrrBC/giphy.gif) 769 | 770 | This one is actually really cool! Can you guess what this is doing? `API.utility.validate( connection.data, { "name": String, "crust": String, "toppings": [ String ] });` Let's jump over and take a look. 771 | 772 |

/server/api/config/api.js

773 | 774 | ```javascript 775 | API = { 776 | utility: { 777 | validate: function( data, pattern ) { 778 | return Match.test( data, pattern ); 779 | } 780 | } 781 | }; 782 | ``` 783 | Okay, pretty simple...but what is this? Remember our friend the `check()` method? This is its little brother (or sister—we don't discriminate here). When we use `check()` remember that it takes a piece of data and then a pattern to test against. If the passed data doesn't match the pattern, `check()` throws an error. 784 | 785 | `Match.test()` is almost identical, with one little difference. Instead of throwing an error (halting any operations on the server), it just returns `true` or `false`. Because we're presumably handling lots of request, we don't want to use `check()` because it could break the API for everyone else. Using `Match.test()` lets us handle each validation and response _to_ that validation independently. 786 | 787 | If you're following along, we're making this reusable to keep everything tidy. Here we pass our data and the pattern we'd like to validate against. Cool! Let's see how make use of the `true`/`false` answer. 788 | 789 |

/server/api/config/api.js

790 | 791 | ```javascript 792 | API = { 793 | methods: { 794 | pizza: { 795 | POST: function( context, connection ) { 796 | var hasData = API.utility.hasData( connection.data ), 797 | validData = API.utility.validate( connection.data, { "name": String, "crust": String, "toppings": [ String ] }); 798 | 799 | if ( hasData && validData ) { 800 | connection.data.owner = connection.owner; 801 | var pizza = Pizza.insert( connection.data ); 802 | API.utility.response( context, 200, { "_id": pizza, "message": "Pizza successfully created!" } ); 803 | } else { 804 | API.utility.response( context, 403, { error: 403, message: "POST calls must have a name, crust, and toppings passed in the request body in the correct formats." } ); 805 | } 806 | }, 807 | } 808 | }, 809 | }; 810 | ``` 811 | 812 | So. Notice that what we're saying in our validation pattern is that in a `POST` request, we expect the data to have a `name` parameter with a type of `String`, a `crust` parameter with a type of `String`, and a `toppings` parameter with a type of `Array` that contains `String`s. We partner the response from this up with our `hasData` method from earlier. Combined, they let us know if we have data to actually _insert_ into the database and if that data is valid. 813 | 814 | If it is, we perform the insert and send back a positive response. If it's not, though, notice that unlike our `GET` method, we don't return any data. Instead, we send back a 403 error (forbidden) along with a message scolding our user. We let them know that their `POST` request must contain the data we're validating against. 815 | 816 | Now we're cruising! Two left: `PUT` and `DELETE`. 817 | 818 | #### PUT Methods 819 | 820 | `PUT` methods are used for _updating_ an existing piece of data. 821 | 822 |

/server/api/config/api.js

823 | 824 | ```javascript 825 | API = { 826 | methods: { 827 | pizza: { 828 | PUT: function() { 829 | var hasQuery = API.utility.hasData( connection.data ), 830 | validData = API.utility.validate( connection.data, Match.OneOf( 831 | { "_id": String, "name": String }, 832 | { "_id": String, "crust": String }, 833 | { "_id": String, "toppings": [ String ] }, 834 | { "_id": String, "name": String, "crust": String }, 835 | { "_id": String, "name": String, "toppings": [ String ] }, 836 | { "_id": String, "crust": String, "toppings": [ String ] }, 837 | { "_id": String, "name": String, "crust": String, "toppings": [ String ] } 838 | )); 839 | 840 | if ( hasQuery && validData ) { 841 | var pizzaId = connection.data._id; 842 | delete connection.data._id; 843 | 844 | var getPizza = Pizza.findOne( { "_id": pizzaId }, { fields: { "_id": 1 } } ); 845 | 846 | if ( getPizza ) { 847 | Pizza.update( { "_id": pizzaId }, { $set: connection.data } ); 848 | API.utility.response( context, 200, { "message": "Pizza successfully updated!" } ); 849 | } else { 850 | API.utility.response( context, 404, { "message": "Can't update a non-existent pizza, homeslice." } ); 851 | } 852 | } else { 853 | API.utility.response( context, 403, { error: 403, message: "PUT calls must have a pizza ID and at least a name, crust, or toppings passed in the request body in the correct formats (String, String, Array)." } ); 854 | } 855 | } 856 | } 857 | }, 858 | }; 859 | ``` 860 | Woah! This isn't as scary as it looks. At this point we're not introducing anything new, just pushing the limits of what we already have. The first thing to point out is our validation, what is that thing? Well. Because a `PUT` request is all about updating existing objects, we may not always be updating using a `1:1` representation of the existing object (read: same fields each time). To compensate, we need to pass all of the different variations of objects we might get from our user. Seriously?! If we want to be on top of our game, yes. Here, we've simply considered each possible permutation of the object we could receive from our users. 861 | 862 | Again, once we have verified that our data exists and is valid, we go to perform the update. First, though, we verify that the document we're trying to update actually exists. If it does, we carry on, if it doesn't we send back an error letting the user know we can't update something that doesn't exist. Just like our `POST` request, if there's no data or it's invalid, we throw a `403` error letting them know to straighten up their act. 863 | 864 | Boom! Last one. Let's do this `DELETE` thing. 865 | 866 | #### DELETE Methods 867 | 868 | I bet you can guess what a `DELETE` request does? Yup! It deletes something. _Mind blow_. Enough sarcasm, let's check it out. 869 | 870 |

/server/api/config/api.js

871 | 872 | ```javascript 873 | API = { 874 | methods: { 875 | pizza: { 876 | DELETE: function( context, connection ) { 877 | var hasQuery = API.utility.hasData( connection.data ), 878 | validData = API.utility.validate( connection.data, { "_id": String } ); 879 | 880 | if ( hasQuery && validData ) { 881 | var pizzaId = connection.data._id; 882 | var getPizza = Pizza.findOne( { "_id": pizzaId }, { fields: { "_id": 1 } } ); 883 | 884 | if ( getPizza ) { 885 | Pizza.remove( { "_id": pizzaId } ); 886 | API.utility.response( context, 200, { "message": "Pizza removed!" } ); 887 | } else { 888 | API.utility.response( context, 404, { "message": "Can't delete a non-existent pizza, homeslice." } ); 889 | } 890 | } else { 891 | API.utility.response( context, 403, { error: 403, message: "DELETE calls must have an _id (and only an _id) in the request body in the correct format (String)." } ); 892 | } 893 | } 894 | } 895 | }, 896 | }; 897 | ``` 898 | Almost _too_ simple, yeah? Our usual suspects `hasData` and `validate` take care of business. We use the same test from our `PUT` method to make sure the document we're trying to delete actually exists and if it does: BLAMMO! If either of our tests fail, we throw a `404` or `403` depending on the case. 899 | 900 |
901 |

Take a bow!

902 |

You just wrote an API, friend! It's simple, but it actually handles all of the basic HTTP methods. We can retrieve, create, update, and delete pizzas. Even the Jetsons didn't have it on lockdown like this.

903 |
904 | 905 | 906 | ### Consuming the API 907 | 908 | Before we part ways, it would be helpful to understand how this API is actually _consumed_ by a user. When we say consumed, we really just mean "used." Like, "I'm so hungry, I'm going to use this burger right now." In order to test our methods out, we can make use of the `http` package. We're not going to do too deep of a dive here. Instead, let's just look at examples of each method, showing how the data can be passed. 909 | 910 | #### GET request 911 | 912 | `GET` requests using the HTTP package will need to pass data using either the `params` object that sits inside of the options object, or, as a string in the `query` parameter formatted like `keyName=value&anotherKey=anotherValue`. That last one, `query`, would be parsed on our server as: 913 | 914 | ```javascript 915 | { 916 | keyName: "value", 917 | anotherKey: "anotherValue" 918 | } 919 | ``` 920 | 921 |

GET Method

922 | 923 | ```javascript 924 | HTTP.get( "http://localhost:3000/api/v1/pizza", { 925 | params: { 926 | "api_key": "Our API key goes here", 927 | "name": "Pizza Name", 928 | "crust": "Crust Name", 929 | "toppings": [ 'an', 'array', 'of', 'toppings' ] 930 | } 931 | }, function( error, response ) { 932 | if ( error ) { 933 | console.log( error ); 934 | } else { 935 | console.log( response ); 936 | } 937 | }); 938 | ``` 939 | 940 | #### POST request 941 | 942 | The `POST` method call is pretty simple. We just pass our data to the `data` object. 943 | 944 |

POST Method

945 | 946 | ```javascript 947 | HTTP.post( "http://localhost:3000/api/v1/pizza", { 948 | data: { 949 | "api_key": "Our API key goes here", 950 | "name": "Pizza Name", 951 | "crust": "Crust Name", 952 | "toppings": [ 'an', 'array', 'of', 'toppings' ] 953 | } 954 | }, function( error, response ) { 955 | if ( error ) { 956 | console.log( error ); 957 | } else { 958 | console.log( response ); 959 | } 960 | }); 961 | ``` 962 | 963 | #### PUT request 964 | 965 | `PUT` is the same, but it can include any of our parameters `name`, `crust`, or `toppings`, but _requires_ an `_id` parameter so we know what pizza to update. 966 | 967 |

PUT Method

968 | 969 | ```javascript 970 | HTTP.put( "http://localhost:3000/api/v1/pizza", { 971 | data: { 972 | "api_key": "Our API key goes here", 973 | "_id": "ID of the pizza to update", 974 | "name": "Pizza Name", 975 | "crust": "Crust Name", 976 | "toppings": [ 'an', 'array', 'of', 'toppings' ] 977 | } 978 | }, function( error, response ) { 979 | if ( error ) { 980 | console.log( error ); 981 | } else { 982 | console.log( response ); 983 | } 984 | }); 985 | ``` 986 | 987 | #### DELETE request 988 | 989 | `DELETE` is the most straightforward. We just need a single `_id` parameter to know which pizza to delete. 990 | 991 |

DELETE Method

992 | 993 | ```javascript 994 | HTTP.del( "http://localhost:3000/api/v1/pizza", { 995 | data: { 996 | "api_key": "Our API key goes here", 997 | _id: "ID of the pizza to delete" 998 | } 999 | }, function( error, response ) { 1000 | if ( error ) { 1001 | console.log( error ); 1002 | } else { 1003 | console.log( response ); 1004 | } 1005 | }); 1006 | ``` 1007 | 1008 | When calling each of these methods (try playing with them in your browser console), you will receive either data or an error back as we defined in each of our response methods! Awesome! 1009 | 1010 | ### Wrap Up & Summary 1011 | 1012 | There we have it! A full blown API, fit for consumption. In this recipe, we learned how to issue and reissue API keys, how to organize our API to keep things reusable and DRY, how to handle requests, how to _respond_ to requests, and finally, how to use the API. This was a lot of work and it is incredibly powerful. Now, you know how to allow other applications and developers to interact with your own application. Using what you learned here, you could build some really cool stuff! 1013 | 1014 | If you come up with your own API, push it to the Meteor servers and share it in the comments! --------------------------------------------------------------------------------