├── .angular-cli.json ├── .babelrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── nodemon.json ├── package.json ├── protractor.conf.js ├── proxy.conf.json ├── server ├── api │ ├── api.js │ └── v1 │ │ ├── api-schemas.js │ │ ├── index.js │ │ └── user │ │ ├── user.api.js │ │ └── user.api.schemas.js ├── app.js ├── config │ └── config.js ├── db │ └── db.js ├── models │ └── user.model.js └── seed │ ├── seed.data.js │ └── seed.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── components │ │ └── header │ │ │ ├── header.component.html │ │ │ ├── header.component.scss │ │ │ ├── header.component.spec.ts │ │ │ └── header.component.ts │ ├── containers │ │ └── home │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ └── users │ │ ├── add-user-modal │ │ ├── add-user-modal.component.html │ │ ├── add-user-modal.component.scss │ │ ├── add-user-modal.component.spec.ts │ │ └── add-user-modal.component.ts │ │ ├── user-list-item │ │ ├── user-list-item.component.html │ │ ├── user-list-item.component.scss │ │ ├── user-list-item.component.spec.ts │ │ └── user-list-item.component.ts │ │ ├── users-list │ │ ├── users-list.component.html │ │ ├── users-list.component.scss │ │ ├── users-list.component.spec.ts │ │ └── users-list.component.ts │ │ ├── users.component.html │ │ ├── users.component.scss │ │ ├── users.component.spec.ts │ │ ├── users.component.ts │ │ ├── users.models.ts │ │ ├── users.module.ts │ │ ├── users.routes.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts ├── assets │ ├── .gitkeep │ ├── angular.png │ ├── fastify.png │ └── ng-fastify.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "angular-fastify-starter" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 23 | "../node_modules/ng2-toastr/bundles/ng2-toastr.min.css", 24 | "styles.scss" 25 | ], 26 | "scripts": [], 27 | "environmentSource": "environments/environment.ts", 28 | "environments": { 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "e2e": { 35 | "protractor": { 36 | "config": "./protractor.conf.js" 37 | } 38 | }, 39 | "lint": [ 40 | { 41 | "project": "src/tsconfig.app.json", 42 | "exclude": "**/node_modules/**" 43 | }, 44 | { 45 | "project": "src/tsconfig.spec.json", 46 | "exclude": "**/node_modules/**" 47 | }, 48 | { 49 | "project": "e2e/tsconfig.e2e.json", 50 | "exclude": "**/node_modules/**" 51 | } 52 | ], 53 | "test": { 54 | "karma": { 55 | "config": "./karma.conf.js" 56 | } 57 | }, 58 | "defaults": { 59 | "styleExt": "scss", 60 | "component": {} 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "env" ] 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | yarn-error.log 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | 45 | # Server logs 46 | server/logs 47 | 48 | # Logs 49 | logs 50 | *.log 51 | npm-debug.log* 52 | yarn-debug.log* 53 | yarn-error.log* 54 | 55 | # Runtime data 56 | pids 57 | *.pid 58 | *.seed 59 | *.pid.lock 60 | 61 | # Directory for instrumented libs generated by jscoverage/JSCover 62 | lib-cov 63 | 64 | # Coverage directory used by tools like istanbul 65 | coverage 66 | 67 | # nyc test coverage 68 | .nyc_output 69 | 70 | # node-waf configuration 71 | .lock-wscript 72 | 73 | # Compiled binary addons (http://nodejs.org/api/addons.html) 74 | build/Release 75 | 76 | # Typescript v1 declaration files 77 | typings/ 78 | 79 | # Optional npm cache directory 80 | .npm 81 | 82 | # Optional eslint cache 83 | .eslintcache 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ahsan Ayaz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularFastifyStarter 2 | 3 |
4 | 5 | ## Technology Stack Used 6 | 7 | - [Angular ( 4.x )](angular.io) 8 | - [Bootstrap 4](getbootstrap.com) 9 | - [NgxBootstrap](https://www.npmjs.com/package/ng2-toastr) 10 | - [Ng2Toastr](valor-software.com/ngx-bootstrap/#/) 11 | - [Fastify](https://github.com/fastify/fastify) 12 | - [FastifyMongoDb](https://github.com/fastify/fastify-mongodb) 13 | - [AngularCLI](https://github.com/angular/angular-cli) 14 | - [Webpack](https://github.com/webpack/webpack) 15 | - [Pino](https://github.com/pinojs/pino) 16 | - [Nodemon](https://nodemon.io/) 17 | - [Concurrently](https://www.npmjs.com/package/concurrently) 18 | 19 | ## Features Covered 20 | 21 | ### Front End 22 | - Angular Smart vs Dumb Components 23 | - Usage of HttpModule 24 | - Showing Toaster alerts via Ng2Toaster 25 | - Lazy Loading Routes (User route) 26 | - Usage of NgxBootstrap i.e. Modal & Collapse (for menu on small screen widths) 27 | 28 | ### Back End 29 | - Fastify as backend framwork 30 | - Fastify Mongo Db extension to connect to mongo db 31 | - Usage of fastify logging 32 | - Writing log stream to file using pino-tee 33 | - Usage of fastify routes, prefixing (e.g. for versioning) 34 | - Usage of fastify schemas for responses and request params/body etc validation 35 | - Usage of babel & babel-preset to be able to use ES6 for backend as well 36 | - Using nodemon & concurrently for serving both front-end and client end while watching too 37 | 38 | ## Install / Development 39 | 40 | ```bash 41 | git clone https://github.com/AhsanAyaz/angular-fastify-starter 42 | cd angular-fastify-starter 43 | 44 | # Install dependencies using npm or yarn 45 | npm install 46 | #or 47 | yarn 48 | 49 | #make sure your mongodb instance is running. If not, do 50 | mongod 51 | #or if required, 52 | sudo mongod 53 | 54 | # start the magic 55 | npm run start 56 | # this will start both client and server 57 | # Client default url: http://localhost:4200 58 | # Server ( fastify app ) API default url: http://localhost:4500 59 | ``` 60 | 61 | ## Proxy for api calls 62 | 63 | The api for the web front-end is proxied using `proxy.conf.json`. Modify as per requirement. 64 | 65 | ## Seed Data 66 | 67 | The server config, i.e. `server/config/config.js` contains the app config for server. 68 | It has a property `seedData` which is by default set to `true`. If it remains `true`, every time the server will start or restart, 69 | the data from `users` collection will be removed and fresh Seed data will be inserted. To disable this, just make `seedData` false. 70 | 71 | ## Client Code scaffolding 72 | 73 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 74 | 75 | ## Client Build 76 | 77 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 78 | 79 | ## Running client unit tests 80 | 81 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 82 | 83 | ## Running client end-to-end tests 84 | 85 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 86 | Before running the tests make sure you are serving the app via `ng serve`. 87 | 88 | # Contribute 89 | 90 | Please feel free to fork, star and contribute to the repository. There surely is a lot of room for improvement for this starter :) -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AngularFastifyStarterPage } from './app.po'; 2 | 3 | describe('angular-fastify-starter App', () => { 4 | let page: AngularFastifyStarterPage; 5 | 6 | beforeEach(() => { 7 | page = new AngularFastifyStarterPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AngularFastifyStarterPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "verbose": false, 4 | "ignore": [ 5 | ".git", 6 | "node_modules/**/node_modules" 7 | ], 8 | "execMap": { 9 | "js": "babel-node" 10 | }, 11 | "watch": [ 12 | "server" 13 | ], 14 | "env": { 15 | "NODE_ENV": "development" 16 | }, 17 | "ext": "js json" 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-fastify-starter", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "node_modules/.bin/concurrently --kill-others \"npm run server:run\" \"ng serve --pc proxy.conf.json --progress false\"", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e", 12 | "server:run": "nodemon --delay 5 server/app.js" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^4.0.0", 17 | "@angular/common": "^4.0.0", 18 | "@angular/compiler": "^4.0.0", 19 | "@angular/core": "^4.0.0", 20 | "@angular/forms": "^4.0.0", 21 | "@angular/http": "^4.0.0", 22 | "@angular/platform-browser": "^4.0.0", 23 | "@angular/platform-browser-dynamic": "^4.0.0", 24 | "@angular/router": "^4.0.0", 25 | "bootstrap": "4.0.0-beta", 26 | "core-js": "^2.4.1", 27 | "fastify": "^0.26.2", 28 | "fastify-mongodb": "^0.1.0", 29 | "ng2-toastr": "^4.1.2", 30 | "ngx-bootstrap": "^1.9.3", 31 | "pino-tee": "^0.2.0", 32 | "rxjs": "^5.4.1", 33 | "zone.js": "^0.8.14" 34 | }, 35 | "devDependencies": { 36 | "@angular/cli": "1.2.7", 37 | "@angular/compiler-cli": "^4.0.0", 38 | "@angular/language-service": "^4.0.0", 39 | "@types/jasmine": "~2.5.53", 40 | "@types/jasminewd2": "~2.0.2", 41 | "@types/node": "~6.0.60", 42 | "babel-cli": "^6.26.0", 43 | "babel-preset-env": "^1.6.0", 44 | "codelyzer": "^3.1.2", 45 | "concurrently": "^3.5.0", 46 | "jasmine-core": "~2.6.2", 47 | "jasmine-spec-reporter": "~4.1.0", 48 | "karma": "~1.7.0", 49 | "karma-chrome-launcher": "~2.1.1", 50 | "karma-cli": "~1.0.1", 51 | "karma-coverage-istanbul-reporter": "^1.2.1", 52 | "karma-jasmine": "~1.1.0", 53 | "karma-jasmine-html-reporter": "^0.2.2", 54 | "nodemon": "^1.12.0", 55 | "protractor": "~5.1.2", 56 | "ts-node": "~3.0.4", 57 | "tslint": "~5.3.2", 58 | "typescript": "~2.3.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:4500", 4 | "secure": false 5 | } 6 | } -------------------------------------------------------------------------------- /server/api/api.js: -------------------------------------------------------------------------------- 1 | import v1Api from './v1'; 2 | 3 | export default (app) => { 4 | v1Api(app); // registering v1 api 5 | } -------------------------------------------------------------------------------- /server/api/v1/api-schemas.js: -------------------------------------------------------------------------------- 1 | // API VERSION LEVEL SCHEMAS CAN RESIDE HERE -------------------------------------------------------------------------------- /server/api/v1/index.js: -------------------------------------------------------------------------------- 1 | import userApi from './user/user.api' 2 | // import more apis here 3 | 4 | export default function(app) { 5 | // register all your v1 apis here 6 | app.register(userApi, { prefix: '/api/v1' }) 7 | } -------------------------------------------------------------------------------- /server/api/v1/user/user.api.js: -------------------------------------------------------------------------------- 1 | import USER_SCHEMAS from './user.api.schemas'; 2 | import assert from 'assert'; 3 | let db, userCollection, ObjectId; 4 | const routeOpts = { 5 | schema: { 6 | 7 | } 8 | } 9 | 10 | // USER API HANDLERS 11 | 12 | const getUsers = (request, reply) => { 13 | userCollection.find().toArray((err, users) => { 14 | if(err) reply.send({ error: err }); 15 | else{ 16 | reply.send({ users }) 17 | } 18 | }) 19 | } 20 | 21 | const addUser = (request, reply) => { 22 | const user = request.body; 23 | userCollection.insert(user, (err, result) => { 24 | assert.equal(err, null); 25 | assert.equal(1, result.result.n); 26 | reply.send({ user: result.ops[0] }) 27 | }) 28 | } 29 | 30 | const deleteUser = (request, reply) => { 31 | request.log.error(request.params); 32 | userCollection.remove({ 33 | _id: new ObjectId(request.params.userId) 34 | }, (err, result) => { 35 | assert.equal(err, null); 36 | assert.equal(1, result.result.n); 37 | reply.send({ result: result.result }) 38 | }) 39 | } 40 | 41 | const getUserById = (request, reply) => { 42 | userCollection.findOne({ 43 | _id: new ObjectId(request.params.userId) 44 | }, (err, result) => { 45 | assert.equal(err, null); 46 | reply.send({ user: result }) 47 | }) 48 | } 49 | 50 | 51 | export default function (app, options, next) { 52 | db = app.mongo.db; 53 | ObjectId = app.mongo.ObjectId; 54 | userCollection = db.collection('users'); 55 | 56 | // ROUTES 57 | 58 | // get users 59 | app.get('/user', Object.assign(routeOpts, { 60 | schema: { 61 | response: USER_SCHEMAS.USERS_RESPONSE 62 | } 63 | }), getUsers); 64 | 65 | // get user by id 66 | app.get('/user/:userId', {} , getUserById); 67 | 68 | // add user 69 | app.post('/user', Object.assign(routeOpts, { 70 | schema: { 71 | body: USER_SCHEMAS.CREATE_USER_BODY, 72 | response: USER_SCHEMAS.USER_RESPONSE 73 | } 74 | }), addUser); 75 | 76 | // delete user 77 | app.delete('/user/:userId', {} , deleteUser); 78 | 79 | next() 80 | } -------------------------------------------------------------------------------- /server/api/v1/user/user.api.schemas.js: -------------------------------------------------------------------------------- 1 | import UserModel from '../../../models/user.model'; 2 | 3 | export const USERS_RESPONSE = { // multi users response 4 | 200: { 5 | type: 'object', 6 | properties: { 7 | users: { type: 'array' , items: UserModel } 8 | } 9 | }, 10 | '4xx': { 11 | type: 'object', 12 | properties: { 13 | error: { type: 'string' }, 14 | message: { type: 'string' } 15 | } 16 | } 17 | } 18 | 19 | export const USER_RESPONSE = { // single user response 20 | 200: { 21 | type: 'object', 22 | properties: { 23 | user: UserModel 24 | } 25 | }, 26 | '4xx': { 27 | type: 'object', 28 | properties: { 29 | error: { type: 'string' }, 30 | message: { type: 'string' } 31 | } 32 | } 33 | } 34 | 35 | export const CREATE_USER_BODY = { // create user body validation 36 | type: 'object', 37 | properties: { 38 | email: { type: 'string' }, 39 | password: { type: 'string' } 40 | } 41 | } 42 | 43 | export const DELETE_USER_PARAMS = { // delete user params validation 44 | userId: { type: 'string' } 45 | } 46 | 47 | export const USER_SCHEMAS = { 48 | USERS_RESPONSE, 49 | USER_RESPONSE, 50 | CREATE_USER_BODY, 51 | DELETE_USER_PARAMS 52 | }; 53 | 54 | export default USER_SCHEMAS; -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | import fastify from 'fastify'; 2 | import registerDB from './db/db'; 3 | import api from './api/api'; 4 | import insertSeedData from './seed/seed'; 5 | import pino from 'pino'; 6 | import tee from 'pino-tee'; 7 | import fs from 'fs'; 8 | import appConfig from './config/config'; 9 | 10 | // Set up stream for logging 11 | const stream = tee(process.stdin); 12 | stream.tee(fs.createWriteStream('server/logs/info'), line => line.level >= 20) 13 | stream.pipe(process.stdout); 14 | 15 | // Register logger for fastify app 16 | const app = fastify({ 17 | logger: pino(stream) 18 | }); 19 | 20 | 21 | // register database (mongo setup for this example) 22 | registerDB(app) 23 | .then(() => { 24 | // when registered, initiate the api 25 | api(app); 26 | 27 | if(appConfig.seedData){ // insert seed data if configured in the appConfig 28 | insertSeedData(app); 29 | } 30 | }) 31 | .catch(err=>{ 32 | console.log(err); 33 | }); 34 | 35 | 36 | // Run the server! 37 | app.listen(appConfig.serverPort || 4500, function (err) { 38 | if (err) throw err 39 | console.log(`server listening on ${app.server.address().port}`) 40 | }) 41 | 42 | 43 | exports = module.exports = app; -------------------------------------------------------------------------------- /server/config/config.js: -------------------------------------------------------------------------------- 1 | const configurations = { 2 | development: { 3 | mongoUrl: 'mongodb://localhost:27017/fastify-app', 4 | seedData: false, 5 | serverPort: 4500 6 | }, 7 | staging: { 8 | mongoUrl: 'mongodb://localhost:27017/fastify-app', 9 | seedData: false, 10 | serverPort: 4500 11 | }, 12 | production: { 13 | mongoUrl: 'mongodb://localhost:27017/fastify-app', 14 | seedData: false, 15 | serverPort: 4500 16 | } 17 | } 18 | 19 | export const APP_ENVIRONMENTS = { 20 | DEV: 'development', 21 | PROD: 'prodoction', 22 | STAGING: 'staging' 23 | } 24 | 25 | const environment = APP_ENVIRONMENTS.DEV; 26 | 27 | 28 | export default configurations[environment]; -------------------------------------------------------------------------------- /server/db/db.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import appConfig from '../config/config'; 4 | import fastifyMongodb from 'fastify-mongodb'; 5 | 6 | const mongodbUrl = appConfig.mongoUrl; 7 | 8 | 9 | /** 10 | * @author Ahsan Ayaz 11 | * The function registers mongodb plugin with our fastify app 12 | * This allows to share the same mongodb throughout the whole app 13 | * @param {fastify app} app 14 | * @return Promise 15 | */ 16 | const registerDB = (app) => { 17 | return new Promise((resolve, reject) => { 18 | app.register(fastifyMongodb, { 19 | url: mongodbUrl 20 | }, err => { 21 | if (err) { 22 | throw err; 23 | }else{ 24 | console.log('Connected to mongo successfully'); 25 | resolve(); 26 | } 27 | }) 28 | }) 29 | } 30 | 31 | 32 | export default registerDB; -------------------------------------------------------------------------------- /server/models/user.model.js: -------------------------------------------------------------------------------- 1 | const UserModel = { 2 | "type": "object", 3 | "properties": { 4 | "cell": { 5 | "type": "string" 6 | }, 7 | "_id": { 8 | "type": "string" 9 | }, 10 | "dob": { 11 | "type": "string" 12 | }, 13 | "email": { 14 | "type": "string" 15 | }, 16 | "gender": { 17 | "type": "string" 18 | }, 19 | "location": { 20 | "properties": { 21 | "city": { 22 | "type": "string" 23 | }, 24 | "postcode": { 25 | "type": "integer" 26 | }, 27 | "state": { 28 | "type": "string" 29 | }, 30 | "street": { 31 | "type": "string" 32 | } 33 | }, 34 | "type": "object" 35 | }, 36 | "login": { 37 | "properties": { 38 | "md5": { 39 | "type": "string" 40 | }, 41 | "password": { 42 | "type": "string" 43 | }, 44 | "salt": { 45 | "type": "string" 46 | }, 47 | "sha1": { 48 | "type": "string" 49 | }, 50 | "sha256": { 51 | "type": "string" 52 | }, 53 | "username": { 54 | "type": "string" 55 | } 56 | }, 57 | "type": "object" 58 | }, 59 | "name": { 60 | "properties": { 61 | "first": { 62 | "type": "string" 63 | }, 64 | "last": { 65 | "type": "string" 66 | }, 67 | "title": { 68 | "type": "string" 69 | } 70 | }, 71 | "type": "object" 72 | }, 73 | "nat": { 74 | "type": "string" 75 | }, 76 | "phone": { 77 | "type": "string" 78 | }, 79 | "picture": { 80 | "properties": { 81 | "large": { 82 | "type": "string" 83 | }, 84 | "medium": { 85 | "type": "string" 86 | }, 87 | "thumbnail": { 88 | "type": "string" 89 | } 90 | }, 91 | "type": "object" 92 | }, 93 | "registered": { 94 | "type": "string" 95 | } 96 | } 97 | } 98 | export default UserModel; -------------------------------------------------------------------------------- /server/seed/seed.data.js: -------------------------------------------------------------------------------- 1 | const seedData = [ 2 | { 3 | "gender":"female", 4 | "name":{ 5 | "title":"miss", 6 | "first":"chloe", 7 | "last":"bergeron" 8 | }, 9 | "location":{ 10 | "street":"8344 college ave", 11 | "city":"enterprise", 12 | "state":"ontario", 13 | "postcode":93109 14 | }, 15 | "email":"chloe.bergeron@example.com", 16 | "login":{ 17 | "username":"lazykoala851", 18 | "password":"jaybird", 19 | "salt":"MIe3JDiI", 20 | "md5":"9882aa5abe1ae75501d3d0ae841c29dc", 21 | "sha1":"3d81ba64de110a0c4847d49b7da5755443fa4a23", 22 | "sha256":"a4057f2e7e0f1b8cf690271c89049433f10ea2f2274c28b4f9b09cf142ef1a14" 23 | }, 24 | "dob":"1968-04-27 13:17:42", 25 | "registered":"2010-12-04 12:39:02", 26 | "phone":"884-178-8875", 27 | "cell":"205-434-4840", 28 | "id":{ 29 | "name":"", 30 | "value":null 31 | }, 32 | "picture":{ 33 | "large":"https://randomuser.me/api/portraits/women/14.jpg", 34 | "medium":"https://randomuser.me/api/portraits/med/women/14.jpg", 35 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/14.jpg" 36 | }, 37 | "nat":"CA" 38 | }, 39 | { 40 | "gender":"male", 41 | "name":{ 42 | "title":"mr", 43 | "first":"adam", 44 | "last":"andersen" 45 | }, 46 | "location":{ 47 | "street":"5928 peel st", 48 | "city":"campbellton", 49 | "state":"nunavut", 50 | "postcode":96643 51 | }, 52 | "email":"adam.andersen@example.com", 53 | "login":{ 54 | "username":"smallbutterfly440", 55 | "password":"huskers", 56 | "salt":"yv55f6VJ", 57 | "md5":"a4bdea053add3a89468a022781cb23bc", 58 | "sha1":"b2be358fb1acac94ac2dd8b8cbefb4058a0c2446", 59 | "sha256":"9d1257de829e3bdcbd66e8b32b2f07b6daa0f3e9625107db585e4fc5233ddeef" 60 | }, 61 | "dob":"1965-02-16 18:28:54", 62 | "registered":"2015-06-18 11:18:51", 63 | "phone":"671-357-0344", 64 | "cell":"242-303-7850", 65 | "id":{ 66 | "name":"", 67 | "value":null 68 | }, 69 | "picture":{ 70 | "large":"https://randomuser.me/api/portraits/men/8.jpg", 71 | "medium":"https://randomuser.me/api/portraits/med/men/8.jpg", 72 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/8.jpg" 73 | }, 74 | "nat":"CA" 75 | }, 76 | { 77 | "gender":"male", 78 | "name":{ 79 | "title":"mr", 80 | "first":"luke", 81 | "last":"brewer" 82 | }, 83 | "location":{ 84 | "street":"2834 park lane", 85 | "city":"athlone", 86 | "state":"kerry", 87 | "postcode":51830 88 | }, 89 | "email":"luke.brewer@example.com", 90 | "login":{ 91 | "username":"organicbird871", 92 | "password":"another", 93 | "salt":"31nw1hfr", 94 | "md5":"2c6766e075921f0bb2ea4dc7c38fbead", 95 | "sha1":"3ee518bc5c1aa4e8850c2d9ceb2dd698bba43879", 96 | "sha256":"db26029386a1fec80d0174055e18bdc2b86056d4b21fa212ecf466329eb6b664" 97 | }, 98 | "dob":"1947-06-27 07:35:19", 99 | "registered":"2010-10-17 16:39:15", 100 | "phone":"011-238-4450", 101 | "cell":"081-834-7928", 102 | "id":{ 103 | "name":"PPS", 104 | "value":"2220834T" 105 | }, 106 | "picture":{ 107 | "large":"https://randomuser.me/api/portraits/men/77.jpg", 108 | "medium":"https://randomuser.me/api/portraits/med/men/77.jpg", 109 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/77.jpg" 110 | }, 111 | "nat":"IE" 112 | }, 113 | { 114 | "gender":"male", 115 | "name":{ 116 | "title":"mr", 117 | "first":"juan", 118 | "last":"arias" 119 | }, 120 | "location":{ 121 | "street":"6009 avenida de américa", 122 | "city":"valladolid", 123 | "state":"la rioja", 124 | "postcode":16243 125 | }, 126 | "email":"juan.arias@example.com", 127 | "login":{ 128 | "username":"organicduck193", 129 | "password":"fordf150", 130 | "salt":"rXDrNaLV", 131 | "md5":"b62b4dbdbe68b6c7074da3a9e24a9544", 132 | "sha1":"d64b97e9e430e0d26180506fcca9f3adc61c370d", 133 | "sha256":"b5fea4974f157d4337ab8804ec3151d35ea3eabf5a8e8959818886a4a911cbf9" 134 | }, 135 | "dob":"1992-10-28 21:11:18", 136 | "registered":"2014-08-06 11:42:49", 137 | "phone":"935-860-773", 138 | "cell":"661-018-644", 139 | "id":{ 140 | "name":"DNI", 141 | "value":"49559417-J" 142 | }, 143 | "picture":{ 144 | "large":"https://randomuser.me/api/portraits/men/28.jpg", 145 | "medium":"https://randomuser.me/api/portraits/med/men/28.jpg", 146 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/28.jpg" 147 | }, 148 | "nat":"ES" 149 | }, 150 | { 151 | "gender":"female", 152 | "name":{ 153 | "title":"mrs", 154 | "first":"nalan", 155 | "last":"demirel" 156 | }, 157 | "location":{ 158 | "street":"2725 anafartalar cd", 159 | "city":"niğde", 160 | "state":"denizli", 161 | "postcode":61500 162 | }, 163 | "email":"nalan.demirel@example.com", 164 | "login":{ 165 | "username":"orangeduck275", 166 | "password":"passwor", 167 | "salt":"zMpJgGgm", 168 | "md5":"7d970824411da0190ad45db4fa987970", 169 | "sha1":"e556b9dd445898e433dc93368546d84c1401721e", 170 | "sha256":"27136f1947460301e6af77e20b495f4888b8393502f0ef9680198163640a8616" 171 | }, 172 | "dob":"1947-07-14 12:49:06", 173 | "registered":"2012-12-26 05:40:59", 174 | "phone":"(582)-051-7271", 175 | "cell":"(286)-068-5174", 176 | "id":{ 177 | "name":"", 178 | "value":null 179 | }, 180 | "picture":{ 181 | "large":"https://randomuser.me/api/portraits/women/56.jpg", 182 | "medium":"https://randomuser.me/api/portraits/med/women/56.jpg", 183 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/56.jpg" 184 | }, 185 | "nat":"TR" 186 | }, 187 | { 188 | "gender":"male", 189 | "name":{ 190 | "title":"mr", 191 | "first":"naël", 192 | "last":"lecomte" 193 | }, 194 | "location":{ 195 | "street":"3551 avenue vauban", 196 | "city":"montreuil", 197 | "state":"gironde", 198 | "postcode":94612 199 | }, 200 | "email":"naël.lecomte@example.com", 201 | "login":{ 202 | "username":"purplefish294", 203 | "password":"lips", 204 | "salt":"5MTVw94R", 205 | "md5":"2d055858d698326498d2f289f28dd62d", 206 | "sha1":"2cb83e7f00220306821ac025462162670b5f03dc", 207 | "sha256":"aaa528de86e95aa8938543f9496f17402577e618efae0327697e46bf53e1d35b" 208 | }, 209 | "dob":"1956-12-12 15:12:23", 210 | "registered":"2010-08-14 11:54:03", 211 | "phone":"05-77-39-49-34", 212 | "cell":"06-46-42-41-81", 213 | "id":{ 214 | "name":"INSEE", 215 | "value":"1561117667588 81" 216 | }, 217 | "picture":{ 218 | "large":"https://randomuser.me/api/portraits/men/13.jpg", 219 | "medium":"https://randomuser.me/api/portraits/med/men/13.jpg", 220 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/13.jpg" 221 | }, 222 | "nat":"FR" 223 | }, 224 | { 225 | "gender":"female", 226 | "name":{ 227 | "title":"ms", 228 | "first":"margarita", 229 | "last":"moreno" 230 | }, 231 | "location":{ 232 | "street":"6491 calle de ángel garcía", 233 | "city":"vigo", 234 | "state":"la rioja", 235 | "postcode":20304 236 | }, 237 | "email":"margarita.moreno@example.com", 238 | "login":{ 239 | "username":"lazyrabbit956", 240 | "password":"25802580", 241 | "salt":"w9brJRkO", 242 | "md5":"3af22c2676e3d63c26d802f4ad4ed714", 243 | "sha1":"8cf2b148ab1a0b8956fca2bb860319b212b7bf23", 244 | "sha256":"ca5bf91527af221739e5d177bacbac25c4ec1493c8f86eddcba85f9692dbf340" 245 | }, 246 | "dob":"1949-05-31 22:04:31", 247 | "registered":"2006-07-17 15:39:49", 248 | "phone":"977-674-612", 249 | "cell":"694-791-978", 250 | "id":{ 251 | "name":"DNI", 252 | "value":"90433638-T" 253 | }, 254 | "picture":{ 255 | "large":"https://randomuser.me/api/portraits/women/14.jpg", 256 | "medium":"https://randomuser.me/api/portraits/med/women/14.jpg", 257 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/14.jpg" 258 | }, 259 | "nat":"ES" 260 | }, 261 | { 262 | "gender":"male", 263 | "name":{ 264 | "title":"mr", 265 | "first":"silas", 266 | "last":"kristensen" 267 | }, 268 | "location":{ 269 | "street":"2135 saturnvej", 270 | "city":"ansager", 271 | "state":"hovedstaden", 272 | "postcode":58804 273 | }, 274 | "email":"silas.kristensen@example.com", 275 | "login":{ 276 | "username":"greenelephant967", 277 | "password":"celica", 278 | "salt":"IpxcAfbj", 279 | "md5":"15b100a5b1d298f7ceef533f637d1e17", 280 | "sha1":"1127397edd52a8c8024036c77519109fcc7ca6d3", 281 | "sha256":"589fb403b94c5b6e50b2ba78d37fb904fc8469c55379e4444b9263d770df1ebb" 282 | }, 283 | "dob":"1957-03-07 13:51:39", 284 | "registered":"2011-01-03 12:42:57", 285 | "phone":"04235176", 286 | "cell":"67911039", 287 | "id":{ 288 | "name":"CPR", 289 | "value":"471033-8764" 290 | }, 291 | "picture":{ 292 | "large":"https://randomuser.me/api/portraits/men/15.jpg", 293 | "medium":"https://randomuser.me/api/portraits/med/men/15.jpg", 294 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/15.jpg" 295 | }, 296 | "nat":"DK" 297 | }, 298 | { 299 | "gender":"female", 300 | "name":{ 301 | "title":"miss", 302 | "first":"emily", 303 | "last":"ruiz" 304 | }, 305 | "location":{ 306 | "street":"7014 chester road", 307 | "city":"bristol", 308 | "state":"grampian", 309 | "postcode":"X3A 6PF" 310 | }, 311 | "email":"emily.ruiz@example.com", 312 | "login":{ 313 | "username":"lazyfrog854", 314 | "password":"asia", 315 | "salt":"Y8vuhTMR", 316 | "md5":"9b6b8dada602fdb4c7c0a929ed21d9d7", 317 | "sha1":"ded722eb76b9328c8a75164465de509538fdca53", 318 | "sha256":"851f6fe802a4b12eee9f94c095a72fc3107f4c9567b951f0d255e741cbc6487c" 319 | }, 320 | "dob":"1961-09-12 00:13:24", 321 | "registered":"2014-03-22 09:57:56", 322 | "phone":"019467 72371", 323 | "cell":"0711-832-949", 324 | "id":{ 325 | "name":"NINO", 326 | "value":"RW 44 98 02 S" 327 | }, 328 | "picture":{ 329 | "large":"https://randomuser.me/api/portraits/women/73.jpg", 330 | "medium":"https://randomuser.me/api/portraits/med/women/73.jpg", 331 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/73.jpg" 332 | }, 333 | "nat":"GB" 334 | }, 335 | { 336 | "gender":"male", 337 | "name":{ 338 | "title":"mr", 339 | "first":"علی", 340 | "last":"محمدخان" 341 | }, 342 | "location":{ 343 | "street":"4043 میدان ولیعصر (عج)", 344 | "city":"کرمانشاه", 345 | "state":"ایلام", 346 | "postcode":52233 347 | }, 348 | "email":"علی.محمدخان@example.com", 349 | "login":{ 350 | "username":"greenelephant143", 351 | "password":"icecream", 352 | "salt":"qebN6uDY", 353 | "md5":"4af8b4b336fe064b97e21216dcf37d5e", 354 | "sha1":"b2eee9c95fa6a7676b43c1ecc07865d75fa84499", 355 | "sha256":"b6677f4c3937abea9dbf02b808955dc83b5a520fd45ac7ab68dff0e5ad502dff" 356 | }, 357 | "dob":"1978-09-05 06:22:29", 358 | "registered":"2004-09-05 02:25:17", 359 | "phone":"033-03364942", 360 | "cell":"0962-072-8439", 361 | "id":{ 362 | "name":"", 363 | "value":null 364 | }, 365 | "picture":{ 366 | "large":"https://randomuser.me/api/portraits/men/27.jpg", 367 | "medium":"https://randomuser.me/api/portraits/med/men/27.jpg", 368 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/27.jpg" 369 | }, 370 | "nat":"IR" 371 | }, 372 | { 373 | "gender":"female", 374 | "name":{ 375 | "title":"miss", 376 | "first":"emilie", 377 | "last":"chu" 378 | }, 379 | "location":{ 380 | "street":"7867 dieppe ave", 381 | "city":"chesterville", 382 | "state":"yukon", 383 | "postcode":27325 384 | }, 385 | "email":"emilie.chu@example.com", 386 | "login":{ 387 | "username":"smallpanda868", 388 | "password":"jetta", 389 | "salt":"LRTNcOMn", 390 | "md5":"444164c78612c602475cea681f18ab62", 391 | "sha1":"9971a45f39b6a1073f125f26718273b1f812490e", 392 | "sha256":"b9627a1d4d78d5a266901e6db5ed42b5477c3744747f6937c7454f90fbfc2cc4" 393 | }, 394 | "dob":"1962-08-12 18:33:05", 395 | "registered":"2007-05-07 17:28:11", 396 | "phone":"094-239-1123", 397 | "cell":"904-101-7493", 398 | "id":{ 399 | "name":"", 400 | "value":null 401 | }, 402 | "picture":{ 403 | "large":"https://randomuser.me/api/portraits/women/37.jpg", 404 | "medium":"https://randomuser.me/api/portraits/med/women/37.jpg", 405 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/37.jpg" 406 | }, 407 | "nat":"CA" 408 | }, 409 | { 410 | "gender":"female", 411 | "name":{ 412 | "title":"miss", 413 | "first":"estelle", 414 | "last":"garnier" 415 | }, 416 | "location":{ 417 | "street":"2673 boulevard de la duchère", 418 | "city":"besançon", 419 | "state":"haut-rhin", 420 | "postcode":71988 421 | }, 422 | "email":"estelle.garnier@example.com", 423 | "login":{ 424 | "username":"organicbear191", 425 | "password":"conner", 426 | "salt":"UFFvOc1r", 427 | "md5":"14dd6ab33c20763545bef81b078dc90f", 428 | "sha1":"cf69bf5fbb051c7bcaf0c7e959e8e6a19d9890ea", 429 | "sha256":"62da260c9369172dfad99616b830f3c75c317518e06f5c21d801a1e0c45c069e" 430 | }, 431 | "dob":"1954-04-17 20:45:40", 432 | "registered":"2009-02-17 19:41:02", 433 | "phone":"03-53-04-07-00", 434 | "cell":"06-29-34-34-75", 435 | "id":{ 436 | "name":"INSEE", 437 | "value":"254373205213 77" 438 | }, 439 | "picture":{ 440 | "large":"https://randomuser.me/api/portraits/women/58.jpg", 441 | "medium":"https://randomuser.me/api/portraits/med/women/58.jpg", 442 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/58.jpg" 443 | }, 444 | "nat":"FR" 445 | }, 446 | { 447 | "gender":"female", 448 | "name":{ 449 | "title":"mrs", 450 | "first":"zoey", 451 | "last":"fisher" 452 | }, 453 | "location":{ 454 | "street":"6075 hogan st", 455 | "city":"shepparton", 456 | "state":"tasmania", 457 | "postcode":1780 458 | }, 459 | "email":"zoey.fisher@example.com", 460 | "login":{ 461 | "username":"beautifulswan693", 462 | "password":"slim", 463 | "salt":"P8wyy76Q", 464 | "md5":"2070ca25539a2335274502d7e1061bfe", 465 | "sha1":"328c4887f921c853cf19906edcbb430efa4ebf9d", 466 | "sha256":"b88c8910ee517bd28feab125f00fbcd53dcd29e8e62615237a94eb3a779bd52a" 467 | }, 468 | "dob":"1967-04-16 23:52:01", 469 | "registered":"2004-04-04 16:43:32", 470 | "phone":"03-9548-8288", 471 | "cell":"0443-028-750", 472 | "id":{ 473 | "name":"TFN", 474 | "value":"860887480" 475 | }, 476 | "picture":{ 477 | "large":"https://randomuser.me/api/portraits/women/28.jpg", 478 | "medium":"https://randomuser.me/api/portraits/med/women/28.jpg", 479 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/28.jpg" 480 | }, 481 | "nat":"AU" 482 | }, 483 | { 484 | "gender":"female", 485 | "name":{ 486 | "title":"miss", 487 | "first":"maeva", 488 | "last":"young" 489 | }, 490 | "location":{ 491 | "street":"6240 20th ave", 492 | "city":"vanier", 493 | "state":"québec", 494 | "postcode":93358 495 | }, 496 | "email":"maeva.young@example.com", 497 | "login":{ 498 | "username":"bluesnake300", 499 | "password":"kikimora", 500 | "salt":"jm3U1U2t", 501 | "md5":"eafb979ed8550e0b9f124755f42eadcc", 502 | "sha1":"151ea751c3e8ea720c13235f07eb84d9842e57c4", 503 | "sha256":"624d5b657c67a20e23bb289706f52cb419fef795dd96ac2f627aa78ec4977c3f" 504 | }, 505 | "dob":"1989-01-21 23:36:58", 506 | "registered":"2015-08-28 23:37:04", 507 | "phone":"447-766-5197", 508 | "cell":"300-293-1061", 509 | "id":{ 510 | "name":"", 511 | "value":null 512 | }, 513 | "picture":{ 514 | "large":"https://randomuser.me/api/portraits/women/32.jpg", 515 | "medium":"https://randomuser.me/api/portraits/med/women/32.jpg", 516 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/32.jpg" 517 | }, 518 | "nat":"CA" 519 | }, 520 | { 521 | "gender":"female", 522 | "name":{ 523 | "title":"mrs", 524 | "first":"eléonore", 525 | "last":"renaud" 526 | }, 527 | "location":{ 528 | "street":"8067 place de l'abbé-basset", 529 | "city":"vitry-sur-seine", 530 | "state":"côte-d'or", 531 | "postcode":57560 532 | }, 533 | "email":"eléonore.renaud@example.com", 534 | "login":{ 535 | "username":"goldenfish100", 536 | "password":"forever", 537 | "salt":"kJxrHTYi", 538 | "md5":"b54fb7fd4ac854578e9027af5edfc80f", 539 | "sha1":"f58c1ebf5c6a48097621b408b531e8be878bf9c7", 540 | "sha256":"5a74903e8deef38120ae86687495cabcd47cc1848a01725a782c757ace64454e" 541 | }, 542 | "dob":"1944-09-26 09:21:09", 543 | "registered":"2006-12-06 15:52:36", 544 | "phone":"03-74-03-66-63", 545 | "cell":"06-37-53-91-76", 546 | "id":{ 547 | "name":"INSEE", 548 | "value":"244857935733 15" 549 | }, 550 | "picture":{ 551 | "large":"https://randomuser.me/api/portraits/women/49.jpg", 552 | "medium":"https://randomuser.me/api/portraits/med/women/49.jpg", 553 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/49.jpg" 554 | }, 555 | "nat":"FR" 556 | }, 557 | { 558 | "gender":"male", 559 | "name":{ 560 | "title":"mr", 561 | "first":"william", 562 | "last":"ambrose" 563 | }, 564 | "location":{ 565 | "street":"7599 tecumseh rd", 566 | "city":"maitland", 567 | "state":"ontario", 568 | "postcode":51758 569 | }, 570 | "email":"william.ambrose@example.com", 571 | "login":{ 572 | "username":"smallmeercat729", 573 | "password":"allan", 574 | "salt":"a3u3YEuq", 575 | "md5":"2133a961b82937e8cb39e7adcb712818", 576 | "sha1":"f5f388d551b15a394240979d2f510647568c4dbc", 577 | "sha256":"49e29de0faa2d8422e57cd01381cc5b618e1c3e9ede079a08b81a44bc864ef16" 578 | }, 579 | "dob":"1946-09-24 19:48:44", 580 | "registered":"2007-03-18 13:47:02", 581 | "phone":"866-982-0782", 582 | "cell":"364-750-9642", 583 | "id":{ 584 | "name":"", 585 | "value":null 586 | }, 587 | "picture":{ 588 | "large":"https://randomuser.me/api/portraits/men/27.jpg", 589 | "medium":"https://randomuser.me/api/portraits/med/men/27.jpg", 590 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/27.jpg" 591 | }, 592 | "nat":"CA" 593 | }, 594 | { 595 | "gender":"female", 596 | "name":{ 597 | "title":"miss", 598 | "first":"sue", 599 | "last":"flores" 600 | }, 601 | "location":{ 602 | "street":"6553 samaritan dr", 603 | "city":"kalgoorlie", 604 | "state":"queensland", 605 | "postcode":227 606 | }, 607 | "email":"sue.flores@example.com", 608 | "login":{ 609 | "username":"brownfrog970", 610 | "password":"super1", 611 | "salt":"4HZgN2ca", 612 | "md5":"ffc6134a095d4251e430cef397c6d7a1", 613 | "sha1":"b7c383fb68f9ce0ef6b1adb1335ec08f14975739", 614 | "sha256":"5a65889c05c057919e0c3a92bf3a9497585b850987bf084b0bfb6fe1906f3a4c" 615 | }, 616 | "dob":"1979-11-15 00:23:47", 617 | "registered":"2006-01-25 15:59:16", 618 | "phone":"06-2735-9596", 619 | "cell":"0412-694-059", 620 | "id":{ 621 | "name":"TFN", 622 | "value":"484593343" 623 | }, 624 | "picture":{ 625 | "large":"https://randomuser.me/api/portraits/women/21.jpg", 626 | "medium":"https://randomuser.me/api/portraits/med/women/21.jpg", 627 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/21.jpg" 628 | }, 629 | "nat":"AU" 630 | }, 631 | { 632 | "gender":"female", 633 | "name":{ 634 | "title":"mrs", 635 | "first":"zoe", 636 | "last":"wilson" 637 | }, 638 | "location":{ 639 | "street":"9760 arctic way", 640 | "city":"chatham", 641 | "state":"alberta", 642 | "postcode":23867 643 | }, 644 | "email":"zoe.wilson@example.com", 645 | "login":{ 646 | "username":"redrabbit677", 647 | "password":"hack", 648 | "salt":"Clz9g53w", 649 | "md5":"a2fdd4626fa1eae873aa577c973ae4a9", 650 | "sha1":"a67bbb0ee2629d324984867f501f211b00fb2473", 651 | "sha256":"275bafc0a8fca43e3648925aff4f921e913c047476ce91b6151d76997eb705bc" 652 | }, 653 | "dob":"1971-08-06 23:01:13", 654 | "registered":"2014-07-06 17:49:39", 655 | "phone":"240-323-5266", 656 | "cell":"452-531-4732", 657 | "id":{ 658 | "name":"", 659 | "value":null 660 | }, 661 | "picture":{ 662 | "large":"https://randomuser.me/api/portraits/women/30.jpg", 663 | "medium":"https://randomuser.me/api/portraits/med/women/30.jpg", 664 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/30.jpg" 665 | }, 666 | "nat":"CA" 667 | }, 668 | { 669 | "gender":"female", 670 | "name":{ 671 | "title":"miss", 672 | "first":"inaya", 673 | "last":"girard" 674 | }, 675 | "location":{ 676 | "street":"8236 rue dubois", 677 | "city":"nantes", 678 | "state":"isère", 679 | "postcode":41724 680 | }, 681 | "email":"inaya.girard@example.com", 682 | "login":{ 683 | "username":"bluepeacock287", 684 | "password":"jericho", 685 | "salt":"sfpOHJkb", 686 | "md5":"80f17554ccda489f0c6e2b3427ffadf0", 687 | "sha1":"c6b637fb7790fd7c508e6028d36ee36f4b6afcd4", 688 | "sha256":"b6521270651f6ca68ada572af9025165a99b574833d4f753274fef4874c6a293" 689 | }, 690 | "dob":"1960-11-28 17:10:42", 691 | "registered":"2002-12-13 19:09:19", 692 | "phone":"04-77-15-10-49", 693 | "cell":"06-10-29-50-03", 694 | "id":{ 695 | "name":"INSEE", 696 | "value":"2601064460158 54" 697 | }, 698 | "picture":{ 699 | "large":"https://randomuser.me/api/portraits/women/1.jpg", 700 | "medium":"https://randomuser.me/api/portraits/med/women/1.jpg", 701 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/1.jpg" 702 | }, 703 | "nat":"FR" 704 | }, 705 | { 706 | "gender":"female", 707 | "name":{ 708 | "title":"miss", 709 | "first":"مریم", 710 | "last":"کریمی" 711 | }, 712 | "location":{ 713 | "street":"1913 دماوند", 714 | "city":"قائم‌شهر", 715 | "state":"سمنان", 716 | "postcode":82066 717 | }, 718 | "email":"مریم.کریمی@example.com", 719 | "login":{ 720 | "username":"lazymeercat864", 721 | "password":"vancouve", 722 | "salt":"jH0C3FEH", 723 | "md5":"03afa04ff093652a4d9cbb7b9642aff8", 724 | "sha1":"3bdc016b99b4b0c5742fecb1ddea29acb0d7639a", 725 | "sha256":"bf3b591ac0e97a4b12646088dff78c2cd77e14e797f266a2d0d3194ad18126ab" 726 | }, 727 | "dob":"1982-01-03 06:27:41", 728 | "registered":"2013-08-10 14:37:32", 729 | "phone":"077-54963401", 730 | "cell":"0995-309-9535", 731 | "id":{ 732 | "name":"", 733 | "value":null 734 | }, 735 | "picture":{ 736 | "large":"https://randomuser.me/api/portraits/women/18.jpg", 737 | "medium":"https://randomuser.me/api/portraits/med/women/18.jpg", 738 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/18.jpg" 739 | }, 740 | "nat":"IR" 741 | }, 742 | { 743 | "gender":"female", 744 | "name":{ 745 | "title":"miss", 746 | "first":"joyce", 747 | "last":"hart" 748 | }, 749 | "location":{ 750 | "street":"1232 homestead rd", 751 | "city":"athens", 752 | "state":"oregon", 753 | "postcode":89840 754 | }, 755 | "email":"joyce.hart@example.com", 756 | "login":{ 757 | "username":"organicgorilla172", 758 | "password":"dancing", 759 | "salt":"sqZ72exx", 760 | "md5":"97b37ad0e1181d7f8f7a5a51ee18d5e6", 761 | "sha1":"742cba67d62b9b0a7c6be8d1b66d97758bc60d03", 762 | "sha256":"b1d0b73f91cafc6894289dae8c592815330a166a05bda1457461c10b3f32f367" 763 | }, 764 | "dob":"1987-05-20 03:02:45", 765 | "registered":"2013-03-24 03:04:39", 766 | "phone":"(243)-781-4886", 767 | "cell":"(888)-797-2826", 768 | "id":{ 769 | "name":"SSN", 770 | "value":"520-58-8438" 771 | }, 772 | "picture":{ 773 | "large":"https://randomuser.me/api/portraits/women/15.jpg", 774 | "medium":"https://randomuser.me/api/portraits/med/women/15.jpg", 775 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/15.jpg" 776 | }, 777 | "nat":"US" 778 | }, 779 | { 780 | "gender":"female", 781 | "name":{ 782 | "title":"miss", 783 | "first":"phoebe", 784 | "last":"chen" 785 | }, 786 | "location":{ 787 | "street":"6464 victoria street", 788 | "city":"wellington", 789 | "state":"west coast", 790 | "postcode":16383 791 | }, 792 | "email":"phoebe.chen@example.com", 793 | "login":{ 794 | "username":"organicdog124", 795 | "password":"10101010", 796 | "salt":"m1KEy4h7", 797 | "md5":"c150dcde0298704a6dcbc32e1860fdc8", 798 | "sha1":"cc1b5bc39811f98aaa0ed560c7d588608cb40456", 799 | "sha256":"697bcf25f7f539df807d38f8eb8c087655dee99c284cf480160877b67baa8a72" 800 | }, 801 | "dob":"1983-06-09 13:08:17", 802 | "registered":"2005-05-07 14:40:23", 803 | "phone":"(830)-622-7639", 804 | "cell":"(076)-726-7724", 805 | "id":{ 806 | "name":"", 807 | "value":null 808 | }, 809 | "picture":{ 810 | "large":"https://randomuser.me/api/portraits/women/76.jpg", 811 | "medium":"https://randomuser.me/api/portraits/med/women/76.jpg", 812 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/76.jpg" 813 | }, 814 | "nat":"NZ" 815 | }, 816 | { 817 | "gender":"male", 818 | "name":{ 819 | "title":"mr", 820 | "first":"frederikke", 821 | "last":"johansen" 822 | }, 823 | "location":{ 824 | "street":"4831 grantofteparken", 825 | "city":"oure", 826 | "state":"syddanmark", 827 | "postcode":63617 828 | }, 829 | "email":"frederikke.johansen@example.com", 830 | "login":{ 831 | "username":"goldenmeercat971", 832 | "password":"lillian", 833 | "salt":"3oIckrSB", 834 | "md5":"6c3266fd8fb7c92b3454b3dfb1116e71", 835 | "sha1":"a72261528a29bd3530cbf0b39fd6d82b18329025", 836 | "sha256":"5cbea62050b3d0c6c49602180cbaf1faa87daaa7f2fa36b7dce045435478c632" 837 | }, 838 | "dob":"1966-05-26 03:31:42", 839 | "registered":"2008-02-22 20:54:43", 840 | "phone":"02889011", 841 | "cell":"05771068", 842 | "id":{ 843 | "name":"CPR", 844 | "value":"568947-9947" 845 | }, 846 | "picture":{ 847 | "large":"https://randomuser.me/api/portraits/men/14.jpg", 848 | "medium":"https://randomuser.me/api/portraits/med/men/14.jpg", 849 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/14.jpg" 850 | }, 851 | "nat":"DK" 852 | }, 853 | { 854 | "gender":"male", 855 | "name":{ 856 | "title":"mr", 857 | "first":"carl", 858 | "last":"olsen" 859 | }, 860 | "location":{ 861 | "street":"7074 skovbyvej", 862 | "city":"viby sj.", 863 | "state":"sjælland", 864 | "postcode":99825 865 | }, 866 | "email":"carl.olsen@example.com", 867 | "login":{ 868 | "username":"ticklishleopard800", 869 | "password":"harley1", 870 | "salt":"N5uL7DZW", 871 | "md5":"4a0fb54fefd45c125435bd4e12924d6f", 872 | "sha1":"0a0ea0bb30bc9943794f2078b67fd9ce20d3e2b4", 873 | "sha256":"b1f28612f31e0beb82fa529f36fd742075e0472b9dbc7fdfd6140f98455947eb" 874 | }, 875 | "dob":"1974-08-28 19:36:51", 876 | "registered":"2015-08-27 06:33:26", 877 | "phone":"51083001", 878 | "cell":"36953854", 879 | "id":{ 880 | "name":"CPR", 881 | "value":"588643-4100" 882 | }, 883 | "picture":{ 884 | "large":"https://randomuser.me/api/portraits/men/35.jpg", 885 | "medium":"https://randomuser.me/api/portraits/med/men/35.jpg", 886 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/35.jpg" 887 | }, 888 | "nat":"DK" 889 | }, 890 | { 891 | "gender":"male", 892 | "name":{ 893 | "title":"monsieur", 894 | "first":"damien", 895 | "last":"rousseau" 896 | }, 897 | "location":{ 898 | "street":"2284 rue baraban", 899 | "city":"morges 1", 900 | "state":"zug", 901 | "postcode":6038 902 | }, 903 | "email":"damien.rousseau@example.com", 904 | "login":{ 905 | "username":"orangeleopard548", 906 | "password":"rage", 907 | "salt":"CBrQZex4", 908 | "md5":"9de803a7f5fa118ed9a1fb4bf3a15e1a", 909 | "sha1":"9e164e7566fc377478044d88528a465b39c802f2", 910 | "sha256":"5cc52b8c6fc2ef799350a1e4e24fc67f06d3c4077e7959ffb222c5d8da1d8e1d" 911 | }, 912 | "dob":"1986-12-16 11:07:41", 913 | "registered":"2012-11-19 03:47:18", 914 | "phone":"(779)-936-3133", 915 | "cell":"(848)-891-8749", 916 | "id":{ 917 | "name":"AVS", 918 | "value":"756.KQJW.HSMG.39" 919 | }, 920 | "picture":{ 921 | "large":"https://randomuser.me/api/portraits/men/32.jpg", 922 | "medium":"https://randomuser.me/api/portraits/med/men/32.jpg", 923 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/32.jpg" 924 | }, 925 | "nat":"CH" 926 | }, 927 | { 928 | "gender":"female", 929 | "name":{ 930 | "title":"miss", 931 | "first":"misty", 932 | "last":"bell" 933 | }, 934 | "location":{ 935 | "street":"6070 hickory creek dr", 936 | "city":"darwin", 937 | "state":"australian capital territory", 938 | "postcode":1634 939 | }, 940 | "email":"misty.bell@example.com", 941 | "login":{ 942 | "username":"bigduck978", 943 | "password":"sf49ers", 944 | "salt":"rZ1UZiQ6", 945 | "md5":"10f044b6b2fb789c54d37609c9b89112", 946 | "sha1":"1a495c1b07337426f1e26900beac83bcbd9f2e57", 947 | "sha256":"a7d3c6b6483b834f7dc2b6ab01a7831da0f31e0650a6b3f3e1b9512271fc7478" 948 | }, 949 | "dob":"1992-03-12 10:21:34", 950 | "registered":"2010-12-02 16:58:01", 951 | "phone":"09-0080-1984", 952 | "cell":"0422-344-352", 953 | "id":{ 954 | "name":"TFN", 955 | "value":"450431926" 956 | }, 957 | "picture":{ 958 | "large":"https://randomuser.me/api/portraits/women/39.jpg", 959 | "medium":"https://randomuser.me/api/portraits/med/women/39.jpg", 960 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/39.jpg" 961 | }, 962 | "nat":"AU" 963 | }, 964 | { 965 | "gender":"male", 966 | "name":{ 967 | "title":"mr", 968 | "first":"james", 969 | "last":"harris" 970 | }, 971 | "location":{ 972 | "street":"8997 anglesea street", 973 | "city":"greymouth", 974 | "state":"tasman", 975 | "postcode":79963 976 | }, 977 | "email":"james.harris@example.com", 978 | "login":{ 979 | "username":"beautifulfish891", 980 | "password":"pimping", 981 | "salt":"IfSYl7Xn", 982 | "md5":"548b1eb89335e9b5f3ff48384280dbfa", 983 | "sha1":"d10918b7dceac87427e14e4b30b019657e837907", 984 | "sha256":"686557ab8895853546e0b6996e9f4f1c039e2916917c3e6dd38e0e343d01369c" 985 | }, 986 | "dob":"1951-01-22 23:07:13", 987 | "registered":"2014-03-16 12:44:20", 988 | "phone":"(261)-699-1512", 989 | "cell":"(471)-720-5163", 990 | "id":{ 991 | "name":"", 992 | "value":null 993 | }, 994 | "picture":{ 995 | "large":"https://randomuser.me/api/portraits/men/69.jpg", 996 | "medium":"https://randomuser.me/api/portraits/med/men/69.jpg", 997 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/69.jpg" 998 | }, 999 | "nat":"NZ" 1000 | }, 1001 | { 1002 | "gender":"female", 1003 | "name":{ 1004 | "title":"miss", 1005 | "first":"marie", 1006 | "last":"gordon" 1007 | }, 1008 | "location":{ 1009 | "street":"3541 oak lawn ave", 1010 | "city":"paterson", 1011 | "state":"iowa", 1012 | "postcode":65213 1013 | }, 1014 | "email":"marie.gordon@example.com", 1015 | "login":{ 1016 | "username":"tinyrabbit229", 1017 | "password":"414141", 1018 | "salt":"zEesSI9v", 1019 | "md5":"58543f4fd8375dce27340cb268ed0353", 1020 | "sha1":"053d7ac7b3a10fa180eab95b6789a017a5b8e4c6", 1021 | "sha256":"a415d8c95b07d15f071e8176305a57d82207fa4100870f360ccb3058234fded6" 1022 | }, 1023 | "dob":"1958-10-21 03:10:21", 1024 | "registered":"2013-12-06 14:52:07", 1025 | "phone":"(154)-255-7987", 1026 | "cell":"(848)-182-7145", 1027 | "id":{ 1028 | "name":"SSN", 1029 | "value":"118-21-6962" 1030 | }, 1031 | "picture":{ 1032 | "large":"https://randomuser.me/api/portraits/women/65.jpg", 1033 | "medium":"https://randomuser.me/api/portraits/med/women/65.jpg", 1034 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/65.jpg" 1035 | }, 1036 | "nat":"US" 1037 | }, 1038 | { 1039 | "gender":"female", 1040 | "name":{ 1041 | "title":"miss", 1042 | "first":"hailey", 1043 | "last":"white" 1044 | }, 1045 | "location":{ 1046 | "street":"7320 york st", 1047 | "city":"cochrane", 1048 | "state":"alberta", 1049 | "postcode":23161 1050 | }, 1051 | "email":"hailey.white@example.com", 1052 | "login":{ 1053 | "username":"whiteswan602", 1054 | "password":"beavis", 1055 | "salt":"5brU25re", 1056 | "md5":"fa3a429094cad0c77c302ad762c81725", 1057 | "sha1":"aaedbae2e904e9c40ad9f09152d702bd57e866e9", 1058 | "sha256":"9e5ada92a5f06aee09474dda1e8a2b65bb2148ff04dadda5825509b2820caf25" 1059 | }, 1060 | "dob":"1946-03-28 12:50:34", 1061 | "registered":"2013-07-09 16:35:07", 1062 | "phone":"316-331-3602", 1063 | "cell":"725-165-7062", 1064 | "id":{ 1065 | "name":"", 1066 | "value":null 1067 | }, 1068 | "picture":{ 1069 | "large":"https://randomuser.me/api/portraits/women/40.jpg", 1070 | "medium":"https://randomuser.me/api/portraits/med/women/40.jpg", 1071 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/40.jpg" 1072 | }, 1073 | "nat":"CA" 1074 | }, 1075 | { 1076 | "gender":"female", 1077 | "name":{ 1078 | "title":"madame", 1079 | "first":"capucine", 1080 | "last":"robin" 1081 | }, 1082 | "location":{ 1083 | "street":"8025 place du 22 novembre 1943", 1084 | "city":"colombier vd", 1085 | "state":"basel-stadt", 1086 | "postcode":7883 1087 | }, 1088 | "email":"capucine.robin@example.com", 1089 | "login":{ 1090 | "username":"organicleopard560", 1091 | "password":"luck", 1092 | "salt":"4DX23Ig6", 1093 | "md5":"d3136a7a060a025aef7aa050b7fb06d2", 1094 | "sha1":"5db126702a93991b23b874a616735e4d286ca3b3", 1095 | "sha256":"a06cbbef4275d58de7829758e87c7ee8e731072e2b69d46d5ba2210f2b8251bc" 1096 | }, 1097 | "dob":"1949-12-14 06:00:44", 1098 | "registered":"2011-05-04 03:54:30", 1099 | "phone":"(144)-535-8418", 1100 | "cell":"(264)-756-0159", 1101 | "id":{ 1102 | "name":"AVS", 1103 | "value":"756.QTLU.MTIW.20" 1104 | }, 1105 | "picture":{ 1106 | "large":"https://randomuser.me/api/portraits/women/32.jpg", 1107 | "medium":"https://randomuser.me/api/portraits/med/women/32.jpg", 1108 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/32.jpg" 1109 | }, 1110 | "nat":"CH" 1111 | }, 1112 | { 1113 | "gender":"female", 1114 | "name":{ 1115 | "title":"ms", 1116 | "first":"mathilda", 1117 | "last":"metzger" 1118 | }, 1119 | "location":{ 1120 | "street":"2059 königsberger straße", 1121 | "city":"suhl", 1122 | "state":"mecklenburg-vorpommern", 1123 | "postcode":34544 1124 | }, 1125 | "email":"mathilda.metzger@example.com", 1126 | "login":{ 1127 | "username":"bigladybug541", 1128 | "password":"iguana", 1129 | "salt":"H0vtSdKR", 1130 | "md5":"77b2e9e545f6aed28a6438797a6eefe9", 1131 | "sha1":"7bec51f5315cae420053131c0540b55a31d06c41", 1132 | "sha256":"a2070a9f18880c2f48bf3f5c25709da8f5b2a9326fa06b5e891175aee593b258" 1133 | }, 1134 | "dob":"1976-05-01 03:20:51", 1135 | "registered":"2013-06-24 21:00:20", 1136 | "phone":"0028-0603124", 1137 | "cell":"0173-6399730", 1138 | "id":{ 1139 | "name":"", 1140 | "value":null 1141 | }, 1142 | "picture":{ 1143 | "large":"https://randomuser.me/api/portraits/women/39.jpg", 1144 | "medium":"https://randomuser.me/api/portraits/med/women/39.jpg", 1145 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/39.jpg" 1146 | }, 1147 | "nat":"DE" 1148 | }, 1149 | { 1150 | "gender":"female", 1151 | "name":{ 1152 | "title":"ms", 1153 | "first":"georgia", 1154 | "last":"wheeler" 1155 | }, 1156 | "location":{ 1157 | "street":"8848 e sandy lake rd", 1158 | "city":"darwin", 1159 | "state":"new south wales", 1160 | "postcode":6040 1161 | }, 1162 | "email":"georgia.wheeler@example.com", 1163 | "login":{ 1164 | "username":"brownfrog912", 1165 | "password":"gilbert", 1166 | "salt":"MsS6HTK9", 1167 | "md5":"c5fd26c1cac6f53a1b18d4f8c144be46", 1168 | "sha1":"8bb577a0d4bc4f27ff27f7b28115ea2f5ca3eed4", 1169 | "sha256":"c148db1fa1aef99afbb2c2774d6ee40c771c3de059b6dc14a7b4141cdb262cde" 1170 | }, 1171 | "dob":"1968-05-05 15:36:56", 1172 | "registered":"2007-07-27 05:05:44", 1173 | "phone":"07-0809-4237", 1174 | "cell":"0439-237-744", 1175 | "id":{ 1176 | "name":"TFN", 1177 | "value":"412631876" 1178 | }, 1179 | "picture":{ 1180 | "large":"https://randomuser.me/api/portraits/women/4.jpg", 1181 | "medium":"https://randomuser.me/api/portraits/med/women/4.jpg", 1182 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/4.jpg" 1183 | }, 1184 | "nat":"AU" 1185 | }, 1186 | { 1187 | "gender":"female", 1188 | "name":{ 1189 | "title":"ms", 1190 | "first":"chloe", 1191 | "last":"davis" 1192 | }, 1193 | "location":{ 1194 | "street":"7184 herbert road", 1195 | "city":"ashbourne", 1196 | "state":"kildare", 1197 | "postcode":21109 1198 | }, 1199 | "email":"chloe.davis@example.com", 1200 | "login":{ 1201 | "username":"blueostrich956", 1202 | "password":"hover", 1203 | "salt":"nowDOn2t", 1204 | "md5":"c7e24ea2552c7d4d0419f5c3628edb02", 1205 | "sha1":"dfff1da7aa1b0fd190423e658f91100dc44d040e", 1206 | "sha256":"5e33e607434429ff6bebf2f9739d18769ad8bcd8e6f9cdb4e65ad32bffd4779a" 1207 | }, 1208 | "dob":"1947-01-09 19:28:18", 1209 | "registered":"2005-07-04 21:57:14", 1210 | "phone":"021-603-6610", 1211 | "cell":"081-355-8323", 1212 | "id":{ 1213 | "name":"PPS", 1214 | "value":"8979146T" 1215 | }, 1216 | "picture":{ 1217 | "large":"https://randomuser.me/api/portraits/women/14.jpg", 1218 | "medium":"https://randomuser.me/api/portraits/med/women/14.jpg", 1219 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/14.jpg" 1220 | }, 1221 | "nat":"IE" 1222 | }, 1223 | { 1224 | "gender":"female", 1225 | "name":{ 1226 | "title":"mrs", 1227 | "first":"marie", 1228 | "last":"stevens" 1229 | }, 1230 | "location":{ 1231 | "street":"3325 main street", 1232 | "city":"carlisle", 1233 | "state":"south yorkshire", 1234 | "postcode":"FT5 0ZU" 1235 | }, 1236 | "email":"marie.stevens@example.com", 1237 | "login":{ 1238 | "username":"bluerabbit137", 1239 | "password":"tasha", 1240 | "salt":"hfHLzR8a", 1241 | "md5":"9f93608dd7f60c35dd742eb1f3241049", 1242 | "sha1":"68440a8e94dcf45d42f2d264c4c0feedf07cd7c6", 1243 | "sha256":"561a026efc3bd6b9b7bbfaf0426ee4c2099d46391c8858314c6dfdb96e2fe6c5" 1244 | }, 1245 | "dob":"1950-11-14 10:58:50", 1246 | "registered":"2005-11-02 23:39:14", 1247 | "phone":"016973 52436", 1248 | "cell":"0716-632-369", 1249 | "id":{ 1250 | "name":"NINO", 1251 | "value":"JM 75 21 83 Q" 1252 | }, 1253 | "picture":{ 1254 | "large":"https://randomuser.me/api/portraits/women/93.jpg", 1255 | "medium":"https://randomuser.me/api/portraits/med/women/93.jpg", 1256 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/93.jpg" 1257 | }, 1258 | "nat":"GB" 1259 | }, 1260 | { 1261 | "gender":"male", 1262 | "name":{ 1263 | "title":"mr", 1264 | "first":"hudson", 1265 | "last":"miller" 1266 | }, 1267 | "location":{ 1268 | "street":"7264 regent ave", 1269 | "city":"enterprise", 1270 | "state":"saskatchewan", 1271 | "postcode":59906 1272 | }, 1273 | "email":"hudson.miller@example.com", 1274 | "login":{ 1275 | "username":"purplebutterfly527", 1276 | "password":"skater", 1277 | "salt":"tDNyYzNS", 1278 | "md5":"87ed3ad0265712e29e977697dc45d139", 1279 | "sha1":"f6f7c5295ce47b0b3d77019c43f70710cfcd404a", 1280 | "sha256":"580f20865172281e33f47ecc359cc282a1172da31e8ce8124ba27c35eb3a8e42" 1281 | }, 1282 | "dob":"1978-07-25 10:00:30", 1283 | "registered":"2006-09-13 09:42:51", 1284 | "phone":"024-705-4856", 1285 | "cell":"121-470-5855", 1286 | "id":{ 1287 | "name":"", 1288 | "value":null 1289 | }, 1290 | "picture":{ 1291 | "large":"https://randomuser.me/api/portraits/men/78.jpg", 1292 | "medium":"https://randomuser.me/api/portraits/med/men/78.jpg", 1293 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/78.jpg" 1294 | }, 1295 | "nat":"CA" 1296 | }, 1297 | { 1298 | "gender":"male", 1299 | "name":{ 1300 | "title":"mr", 1301 | "first":"miguel", 1302 | "last":"reyes" 1303 | }, 1304 | "location":{ 1305 | "street":"2988 calle de toledo", 1306 | "city":"burgos", 1307 | "state":"galicia", 1308 | "postcode":53459 1309 | }, 1310 | "email":"miguel.reyes@example.com", 1311 | "login":{ 1312 | "username":"tinyleopard711", 1313 | "password":"gizmo1", 1314 | "salt":"ijueOITN", 1315 | "md5":"315273992e68cbb3b1427483376c361f", 1316 | "sha1":"9a30c4fa6d2d7d459d079c3801466b13cc793e19", 1317 | "sha256":"b7a610794c284376394cfd7e0c18f16f44c40a31271e188119c17298b8b21d66" 1318 | }, 1319 | "dob":"1971-10-18 17:43:28", 1320 | "registered":"2008-07-02 00:52:55", 1321 | "phone":"990-951-814", 1322 | "cell":"684-387-502", 1323 | "id":{ 1324 | "name":"DNI", 1325 | "value":"21379429-P" 1326 | }, 1327 | "picture":{ 1328 | "large":"https://randomuser.me/api/portraits/men/95.jpg", 1329 | "medium":"https://randomuser.me/api/portraits/med/men/95.jpg", 1330 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/95.jpg" 1331 | }, 1332 | "nat":"ES" 1333 | }, 1334 | { 1335 | "gender":"female", 1336 | "name":{ 1337 | "title":"mrs", 1338 | "first":"sophia", 1339 | "last":"long" 1340 | }, 1341 | "location":{ 1342 | "street":"7627 ash dr", 1343 | "city":"australian capital territory", 1344 | "state":"new south wales", 1345 | "postcode":319 1346 | }, 1347 | "email":"sophia.long@example.com", 1348 | "login":{ 1349 | "username":"tinybird884", 1350 | "password":"rugby", 1351 | "salt":"62MLjtCP", 1352 | "md5":"d48b1eb07234d34565dc198905afce8c", 1353 | "sha1":"803a08cf5d55c06b2ac1aa388bd57ef59939a3b0", 1354 | "sha256":"f28d02c455817ee5b0c44681ea7d1332dfa27cd8c83c2bffa55657d75f91df4b" 1355 | }, 1356 | "dob":"1954-05-08 10:21:23", 1357 | "registered":"2009-01-26 15:27:36", 1358 | "phone":"07-4921-9146", 1359 | "cell":"0435-146-126", 1360 | "id":{ 1361 | "name":"TFN", 1362 | "value":"221302712" 1363 | }, 1364 | "picture":{ 1365 | "large":"https://randomuser.me/api/portraits/women/34.jpg", 1366 | "medium":"https://randomuser.me/api/portraits/med/women/34.jpg", 1367 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/34.jpg" 1368 | }, 1369 | "nat":"AU" 1370 | }, 1371 | { 1372 | "gender":"male", 1373 | "name":{ 1374 | "title":"mr", 1375 | "first":"joaquin", 1376 | "last":"fernandez" 1377 | }, 1378 | "location":{ 1379 | "street":"6592 ronda de toledo", 1380 | "city":"hospitalet de llobregat", 1381 | "state":"ceuta", 1382 | "postcode":54666 1383 | }, 1384 | "email":"joaquin.fernandez@example.com", 1385 | "login":{ 1386 | "username":"silverladybug723", 1387 | "password":"thomas1", 1388 | "salt":"ZEFHU8P4", 1389 | "md5":"d7837e0fded6b0828d7e46ab9050b5ad", 1390 | "sha1":"d9a02aace7a8a0a4fd2086036e6226a7d6db9d53", 1391 | "sha256":"560e38c8959e34ff97ae5ef2a249648eea59b89896114a361803a42881857074" 1392 | }, 1393 | "dob":"1977-01-08 07:12:17", 1394 | "registered":"2010-10-21 22:43:05", 1395 | "phone":"920-256-337", 1396 | "cell":"605-281-879", 1397 | "id":{ 1398 | "name":"DNI", 1399 | "value":"20867378-N" 1400 | }, 1401 | "picture":{ 1402 | "large":"https://randomuser.me/api/portraits/men/87.jpg", 1403 | "medium":"https://randomuser.me/api/portraits/med/men/87.jpg", 1404 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/87.jpg" 1405 | }, 1406 | "nat":"ES" 1407 | }, 1408 | { 1409 | "gender":"male", 1410 | "name":{ 1411 | "title":"mr", 1412 | "first":"abraham", 1413 | "last":"coremans" 1414 | }, 1415 | "location":{ 1416 | "street":"3986 minrebroederstraat", 1417 | "city":"lochem", 1418 | "state":"limburg", 1419 | "postcode":76821 1420 | }, 1421 | "email":"abraham.coremans@example.com", 1422 | "login":{ 1423 | "username":"smallostrich922", 1424 | "password":"456654", 1425 | "salt":"zAWCYCAr", 1426 | "md5":"005fcc956435697f104c0988b228e9a9", 1427 | "sha1":"5125bea3894bec3e3c8d9472892836a3bb0126d7", 1428 | "sha256":"02ecd410ed0524a304dc64468e10927e06e9fe2918b8628245fb926e294c51d8" 1429 | }, 1430 | "dob":"1968-10-12 21:36:51", 1431 | "registered":"2016-07-11 05:11:57", 1432 | "phone":"(762)-235-2767", 1433 | "cell":"(264)-333-4953", 1434 | "id":{ 1435 | "name":"BSN", 1436 | "value":"67748916" 1437 | }, 1438 | "picture":{ 1439 | "large":"https://randomuser.me/api/portraits/men/45.jpg", 1440 | "medium":"https://randomuser.me/api/portraits/med/men/45.jpg", 1441 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/45.jpg" 1442 | }, 1443 | "nat":"NL" 1444 | }, 1445 | { 1446 | "gender":"female", 1447 | "name":{ 1448 | "title":"ms", 1449 | "first":"تینا", 1450 | "last":"یاسمی" 1451 | }, 1452 | "location":{ 1453 | "street":"7157 شهید گلپایگانی", 1454 | "city":"بابل", 1455 | "state":"بوشهر", 1456 | "postcode":11528 1457 | }, 1458 | "email":"تینا.یاسمی@example.com", 1459 | "login":{ 1460 | "username":"crazyostrich428", 1461 | "password":"garage", 1462 | "salt":"7vaM4dD5", 1463 | "md5":"e67b0ba4e522d4585466bc3e7f7acd0f", 1464 | "sha1":"a7a113b18d377561c50155e1eac18091ed72bb33", 1465 | "sha256":"0f5a3bb4a7902501612c5cc04b0ad425647c106030e894f0e6261bef45f9baa2" 1466 | }, 1467 | "dob":"1949-04-20 14:27:17", 1468 | "registered":"2007-06-20 19:23:14", 1469 | "phone":"073-33044623", 1470 | "cell":"0951-173-7896", 1471 | "id":{ 1472 | "name":"", 1473 | "value":null 1474 | }, 1475 | "picture":{ 1476 | "large":"https://randomuser.me/api/portraits/women/20.jpg", 1477 | "medium":"https://randomuser.me/api/portraits/med/women/20.jpg", 1478 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/20.jpg" 1479 | }, 1480 | "nat":"IR" 1481 | }, 1482 | { 1483 | "gender":"male", 1484 | "name":{ 1485 | "title":"mr", 1486 | "first":"johnny", 1487 | "last":"thomas" 1488 | }, 1489 | "location":{ 1490 | "street":"3854 green rd", 1491 | "city":"centennial", 1492 | "state":"colorado", 1493 | "postcode":37365 1494 | }, 1495 | "email":"johnny.thomas@example.com", 1496 | "login":{ 1497 | "username":"heavydog142", 1498 | "password":"fitter", 1499 | "salt":"7asMzEoX", 1500 | "md5":"d1a65390366baa63652a46186d71d3c1", 1501 | "sha1":"642e475ce8e924a0f8be286ecbaaa0088f301bb7", 1502 | "sha256":"8584584142f7b224a90235c0acfe75263481b5284e8eef9685fa7f55e88073d2" 1503 | }, 1504 | "dob":"1954-08-25 10:42:35", 1505 | "registered":"2012-12-27 04:03:56", 1506 | "phone":"(129)-162-9373", 1507 | "cell":"(815)-118-3713", 1508 | "id":{ 1509 | "name":"SSN", 1510 | "value":"965-58-9877" 1511 | }, 1512 | "picture":{ 1513 | "large":"https://randomuser.me/api/portraits/men/2.jpg", 1514 | "medium":"https://randomuser.me/api/portraits/med/men/2.jpg", 1515 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/2.jpg" 1516 | }, 1517 | "nat":"US" 1518 | }, 1519 | { 1520 | "gender":"male", 1521 | "name":{ 1522 | "title":"mr", 1523 | "first":"christopher", 1524 | "last":"morris" 1525 | }, 1526 | "location":{ 1527 | "street":"9726 daisy dr", 1528 | "city":"nowra", 1529 | "state":"western australia", 1530 | "postcode":6946 1531 | }, 1532 | "email":"christopher.morris@example.com", 1533 | "login":{ 1534 | "username":"purpleleopard490", 1535 | "password":"memorex", 1536 | "salt":"WB2SymKz", 1537 | "md5":"66df34d7b508d56e67fcae269f930bb5", 1538 | "sha1":"6b7c701a6bb5eee5227febed35b6fd5e91c2d77e", 1539 | "sha256":"75f9d63262e2f7b63c7883e3bafdc6ea863f8c94dbabeac8939cdc8846ab4596" 1540 | }, 1541 | "dob":"1990-01-20 07:49:48", 1542 | "registered":"2007-11-29 04:01:02", 1543 | "phone":"07-5802-1020", 1544 | "cell":"0488-128-495", 1545 | "id":{ 1546 | "name":"TFN", 1547 | "value":"832032869" 1548 | }, 1549 | "picture":{ 1550 | "large":"https://randomuser.me/api/portraits/men/35.jpg", 1551 | "medium":"https://randomuser.me/api/portraits/med/men/35.jpg", 1552 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/35.jpg" 1553 | }, 1554 | "nat":"AU" 1555 | }, 1556 | { 1557 | "gender":"male", 1558 | "name":{ 1559 | "title":"mr", 1560 | "first":"ایلیا", 1561 | "last":"رضاییان" 1562 | }, 1563 | "location":{ 1564 | "street":"1160 میدان شهدا", 1565 | "city":"نیشابور", 1566 | "state":"خوزستان", 1567 | "postcode":10213 1568 | }, 1569 | "email":"ایلیا.رضاییان@example.com", 1570 | "login":{ 1571 | "username":"whitepeacock340", 1572 | "password":"1215", 1573 | "salt":"DY0jiiEe", 1574 | "md5":"e1b4f9bea3ebf886fe1e6385c49afe06", 1575 | "sha1":"d7ea744bfafea2daf0d4b5b9dadf86728d1984cd", 1576 | "sha256":"403df1bc692ea50b020064503d7d12e4dc6cebab89aeba10ea6ab7a6143ca100" 1577 | }, 1578 | "dob":"1958-03-19 02:22:00", 1579 | "registered":"2005-12-19 17:37:20", 1580 | "phone":"047-21571965", 1581 | "cell":"0944-283-2404", 1582 | "id":{ 1583 | "name":"", 1584 | "value":null 1585 | }, 1586 | "picture":{ 1587 | "large":"https://randomuser.me/api/portraits/men/6.jpg", 1588 | "medium":"https://randomuser.me/api/portraits/med/men/6.jpg", 1589 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/6.jpg" 1590 | }, 1591 | "nat":"IR" 1592 | }, 1593 | { 1594 | "gender":"male", 1595 | "name":{ 1596 | "title":"mr", 1597 | "first":"mahmoud", 1598 | "last":"drenthen" 1599 | }, 1600 | "location":{ 1601 | "street":"6153 paardenveld", 1602 | "city":"purmerend", 1603 | "state":"noord-holland", 1604 | "postcode":18020 1605 | }, 1606 | "email":"mahmoud.drenthen@example.com", 1607 | "login":{ 1608 | "username":"whitekoala882", 1609 | "password":"zxc123", 1610 | "salt":"V6X9QvsT", 1611 | "md5":"36a63b6785aa44a1c4be629ac1a5c939", 1612 | "sha1":"8cf311a00edf5dfff07ffa1eb944745b970efbef", 1613 | "sha256":"b974b8de03f6efb6920b5ec28efee19db67f54716d14d19ed40a58aabc6d4606" 1614 | }, 1615 | "dob":"1962-03-21 12:52:08", 1616 | "registered":"2010-07-03 23:43:31", 1617 | "phone":"(606)-283-8816", 1618 | "cell":"(652)-164-4006", 1619 | "id":{ 1620 | "name":"BSN", 1621 | "value":"43152752" 1622 | }, 1623 | "picture":{ 1624 | "large":"https://randomuser.me/api/portraits/men/96.jpg", 1625 | "medium":"https://randomuser.me/api/portraits/med/men/96.jpg", 1626 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/96.jpg" 1627 | }, 1628 | "nat":"NL" 1629 | }, 1630 | { 1631 | "gender":"male", 1632 | "name":{ 1633 | "title":"mr", 1634 | "first":"eli", 1635 | "last":"morris" 1636 | }, 1637 | "location":{ 1638 | "street":"2028 titahi bay road", 1639 | "city":"upper hutt", 1640 | "state":"taranaki", 1641 | "postcode":17909 1642 | }, 1643 | "email":"eli.morris@example.com", 1644 | "login":{ 1645 | "username":"silverrabbit543", 1646 | "password":"apache", 1647 | "salt":"1vb2zHi3", 1648 | "md5":"012392a91d163811ec25f6f0769381f8", 1649 | "sha1":"4b4c0e4c20d5915c3944a4791421d4c823434ae8", 1650 | "sha256":"c7f06e254de4d1e8ca3ccf1ff3beea93134cef8b94beb81825c431ec6661d024" 1651 | }, 1652 | "dob":"1969-10-10 13:08:38", 1653 | "registered":"2013-08-14 18:23:38", 1654 | "phone":"(779)-630-3185", 1655 | "cell":"(637)-236-2277", 1656 | "id":{ 1657 | "name":"", 1658 | "value":null 1659 | }, 1660 | "picture":{ 1661 | "large":"https://randomuser.me/api/portraits/men/50.jpg", 1662 | "medium":"https://randomuser.me/api/portraits/med/men/50.jpg", 1663 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/50.jpg" 1664 | }, 1665 | "nat":"NZ" 1666 | }, 1667 | { 1668 | "gender":"male", 1669 | "name":{ 1670 | "title":"mr", 1671 | "first":"hugo", 1672 | "last":"jackson" 1673 | }, 1674 | "location":{ 1675 | "street":"9140 ronwood avenue", 1676 | "city":"tauranga", 1677 | "state":"wellington", 1678 | "postcode":84614 1679 | }, 1680 | "email":"hugo.jackson@example.com", 1681 | "login":{ 1682 | "username":"ticklishfish734", 1683 | "password":"pancake", 1684 | "salt":"GdAj6Gnc", 1685 | "md5":"698071f49899acca5348c16841ef9e63", 1686 | "sha1":"cdc877673e49aa8d3d71a31ba19dc8a44a673dd1", 1687 | "sha256":"093df6db8153e79e20878b142e3ff36747346d560cf8672019058c445831168c" 1688 | }, 1689 | "dob":"1957-07-27 07:18:37", 1690 | "registered":"2002-12-03 01:57:57", 1691 | "phone":"(629)-494-7546", 1692 | "cell":"(155)-686-6174", 1693 | "id":{ 1694 | "name":"", 1695 | "value":null 1696 | }, 1697 | "picture":{ 1698 | "large":"https://randomuser.me/api/portraits/men/39.jpg", 1699 | "medium":"https://randomuser.me/api/portraits/med/men/39.jpg", 1700 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/39.jpg" 1701 | }, 1702 | "nat":"NZ" 1703 | }, 1704 | { 1705 | "gender":"female", 1706 | "name":{ 1707 | "title":"miss", 1708 | "first":"elya", 1709 | "last":"lopez" 1710 | }, 1711 | "location":{ 1712 | "street":"6981 rue du stade", 1713 | "city":"créteil", 1714 | "state":"vendée", 1715 | "postcode":97647 1716 | }, 1717 | "email":"elya.lopez@example.com", 1718 | "login":{ 1719 | "username":"lazyswan254", 1720 | "password":"master", 1721 | "salt":"JNCM59bo", 1722 | "md5":"6012d5b06421a07427b96956124fa28e", 1723 | "sha1":"2c64b42689418380ed4c6525da8f1b636f4ad2e1", 1724 | "sha256":"91edf6ca6aab5f88e3a675065eccc2a4fc8ca3b9104cefd29770f159f468f43e" 1725 | }, 1726 | "dob":"1984-01-07 14:23:45", 1727 | "registered":"2003-05-27 02:55:51", 1728 | "phone":"02-69-54-91-89", 1729 | "cell":"06-15-97-10-75", 1730 | "id":{ 1731 | "name":"INSEE", 1732 | "value":"284063370589 97" 1733 | }, 1734 | "picture":{ 1735 | "large":"https://randomuser.me/api/portraits/women/53.jpg", 1736 | "medium":"https://randomuser.me/api/portraits/med/women/53.jpg", 1737 | "thumbnail":"https://randomuser.me/api/portraits/thumb/women/53.jpg" 1738 | }, 1739 | "nat":"FR" 1740 | }, 1741 | { 1742 | "gender":"male", 1743 | "name":{ 1744 | "title":"mr", 1745 | "first":"nixon", 1746 | "last":"patel" 1747 | }, 1748 | "location":{ 1749 | "street":"8537 elliot street", 1750 | "city":"blenheim", 1751 | "state":"canterbury", 1752 | "postcode":97383 1753 | }, 1754 | "email":"nixon.patel@example.com", 1755 | "login":{ 1756 | "username":"beautifulmeercat379", 1757 | "password":"sinner", 1758 | "salt":"hexhsAOY", 1759 | "md5":"a4dbaf29f66e2bfe49832f0c1fa95a5d", 1760 | "sha1":"0021a3753e3ba4225ef81bb5fe4f43573e2cea57", 1761 | "sha256":"a5544b331de656902a3dbc492f3b25f78cfadfb9597da84b92eb1151a5c23910" 1762 | }, 1763 | "dob":"1964-05-24 17:50:56", 1764 | "registered":"2013-11-23 15:14:12", 1765 | "phone":"(574)-489-9456", 1766 | "cell":"(955)-946-7082", 1767 | "id":{ 1768 | "name":"", 1769 | "value":null 1770 | }, 1771 | "picture":{ 1772 | "large":"https://randomuser.me/api/portraits/men/97.jpg", 1773 | "medium":"https://randomuser.me/api/portraits/med/men/97.jpg", 1774 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/97.jpg" 1775 | }, 1776 | "nat":"NZ" 1777 | }, 1778 | { 1779 | "gender":"male", 1780 | "name":{ 1781 | "title":"mr", 1782 | "first":"janne", 1783 | "last":"baars" 1784 | }, 1785 | "location":{ 1786 | "street":"7970 sterrenburg", 1787 | "city":"huizen", 1788 | "state":"groningen", 1789 | "postcode":74926 1790 | }, 1791 | "email":"janne.baars@example.com", 1792 | "login":{ 1793 | "username":"beautifuldog187", 1794 | "password":"last", 1795 | "salt":"WTxMzvkM", 1796 | "md5":"b32b431585cb6a4379386a2e6fe22c1e", 1797 | "sha1":"8f0d0caf380532fbe09667029f9d2ea68a2c9586", 1798 | "sha256":"b75505d14dbcd8616ca225f0e1337d5c67cb7aeabf3cb89a0bdc414308f8bf23" 1799 | }, 1800 | "dob":"1969-10-22 10:22:53", 1801 | "registered":"2008-01-07 20:53:40", 1802 | "phone":"(820)-764-8571", 1803 | "cell":"(355)-997-8038", 1804 | "id":{ 1805 | "name":"BSN", 1806 | "value":"98409671" 1807 | }, 1808 | "picture":{ 1809 | "large":"https://randomuser.me/api/portraits/men/33.jpg", 1810 | "medium":"https://randomuser.me/api/portraits/med/men/33.jpg", 1811 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/33.jpg" 1812 | }, 1813 | "nat":"NL" 1814 | }, 1815 | { 1816 | "gender":"male", 1817 | "name":{ 1818 | "title":"mr", 1819 | "first":"john", 1820 | "last":"wood" 1821 | }, 1822 | "location":{ 1823 | "street":"8889 highgate", 1824 | "city":"tauranga", 1825 | "state":"manawatu-wanganui", 1826 | "postcode":69259 1827 | }, 1828 | "email":"john.wood@example.com", 1829 | "login":{ 1830 | "username":"bigkoala901", 1831 | "password":"volley", 1832 | "salt":"lg2HfKQX", 1833 | "md5":"3f827aae4a72e0d6a7aafd9c2b773590", 1834 | "sha1":"32afc58b0afb832f996db193a822a30b92e49db4", 1835 | "sha256":"d070324588ef4da79e884757c1dae287958473e4084daffef4f4392ff01208fd" 1836 | }, 1837 | "dob":"1956-05-31 11:28:49", 1838 | "registered":"2006-12-15 21:23:18", 1839 | "phone":"(100)-455-5811", 1840 | "cell":"(242)-012-8783", 1841 | "id":{ 1842 | "name":"", 1843 | "value":null 1844 | }, 1845 | "picture":{ 1846 | "large":"https://randomuser.me/api/portraits/men/42.jpg", 1847 | "medium":"https://randomuser.me/api/portraits/med/men/42.jpg", 1848 | "thumbnail":"https://randomuser.me/api/portraits/thumb/men/42.jpg" 1849 | }, 1850 | "nat":"NZ" 1851 | } 1852 | ]; 1853 | 1854 | export default seedData; -------------------------------------------------------------------------------- /server/seed/seed.js: -------------------------------------------------------------------------------- 1 | import seedData from './seed.data'; 2 | import appConfig from '../config/config'; 3 | import assert from 'assert'; 4 | 5 | 6 | /** 7 | * @author Ahsan Ayaz 8 | * The function adds the seed data from seed.data.js into the database 9 | * On every restart/start of the server, the previous data will we be WIPED out and 10 | * seed data will be inserted. 11 | * To avoid this behavior, set seedData property to false in appConfig 12 | */ 13 | const insertSeedData = (app) => { 14 | const { db } = app.mongo; 15 | const usersCollection = db.collection('users'); 16 | usersCollection.remove({}, (err) => { 17 | assert.equal(null, err); 18 | usersCollection.insertMany(seedData, (err, result) => { 19 | assert.equal(null, err); 20 | console.log('seed data entered'); 21 | }); 22 | }) 23 | } 24 | 25 | 26 | export default insertSeedData; -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './containers/home/home.component'; 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | pathMatch: 'full', 8 | component: HomeComponent, 9 | children: [] 10 | }, 11 | { loadChildren: 'app/users/users.module#UsersModule', path: 'users' } // Lazy loaded `users` route 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forRoot(routes, { 16 | useHash: true 17 | })], 18 | exports: [RouterModule] 19 | }) 20 | export class AppRoutingModule { } 21 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule 11 | ], 12 | declarations: [ 13 | AppComponent 14 | ], 15 | }).compileComponents(); 16 | })); 17 | 18 | it('should create the app', async(() => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.debugElement.componentInstance; 21 | expect(app).toBeTruthy(); 22 | })); 23 | 24 | it(`should have as title 'app'`, async(() => { 25 | const fixture = TestBed.createComponent(AppComponent); 26 | const app = fixture.debugElement.componentInstance; 27 | expect(app.title).toEqual('app'); 28 | })); 29 | 30 | it('should render title in a h1 tag', async(() => { 31 | const fixture = TestBed.createComponent(AppComponent); 32 | fixture.detectChanges(); 33 | const compiled = fixture.debugElement.nativeElement; 34 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); 35 | })); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewContainerRef } from '@angular/core'; 2 | import { ToastsManager } from 'ng2-toastr'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | constructor(private vcr: ViewContainerRef, private toastr: ToastsManager) { 11 | // Setting toastr's root container 12 | this.toastr.setRootViewContainerRef(vcr); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { ToastModule } from 'ng2-toastr/ng2-toastr'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { AppComponent } from './app.component'; 8 | import { HeaderComponent } from './components/header/header.component'; 9 | 10 | // Bootstrap modules 11 | import { CollapseModule } from 'ngx-bootstrap/collapse'; 12 | import { HomeComponent } from './containers/home/home.component'; 13 | 14 | 15 | 16 | @NgModule({ 17 | declarations: [ 18 | AppComponent, 19 | HeaderComponent, 20 | HomeComponent 21 | ], 22 | imports: [ 23 | BrowserModule, 24 | AppRoutingModule, 25 | CollapseModule.forRoot(), 26 | ToastModule.forRoot(), 27 | BrowserAnimationsModule 28 | ], 29 | providers: [], 30 | bootstrap: [AppComponent] 31 | }) 32 | export class AppModule { } 33 | -------------------------------------------------------------------------------- /src/app/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/header/header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/app/components/header/header.component.scss -------------------------------------------------------------------------------- /src/app/components/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header', 5 | templateUrl: './header.component.html', 6 | styleUrls: ['./header.component.scss'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | export class HeaderComponent implements OnInit { 10 | isCollapsed = true; 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | /** 17 | * @author Ahsan Ayaz 18 | * Toggles the menu in lower screen widths 19 | */ 20 | toggleCollapse() { 21 | this.isCollapsed = !this.isCollapsed; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/containers/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{title}}! 4 |

5 |
6 | 7 | 8 |
9 |
-------------------------------------------------------------------------------- /src/app/containers/home/home.component.scss: -------------------------------------------------------------------------------- 1 | .home-container{ 2 | margin-top: 30px; 3 | text-align: center; 4 | .logos{ 5 | width: 630px; 6 | height: 300px; 7 | margin: 0 auto; 8 | display: flex; 9 | flex-direction: row; 10 | img{ 11 | height: 100%; 12 | object-fit: contain; 13 | max-width: 280px; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/containers/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/containers/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | title = 'Angular Fastify Starter'; 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/users/add-user-modal/add-user-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 19 | 23 |
24 | -------------------------------------------------------------------------------- /src/app/users/add-user-modal/add-user-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/app/users/add-user-modal/add-user-modal.component.scss -------------------------------------------------------------------------------- /src/app/users/add-user-modal/add-user-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddUserModalComponent } from './add-user-modal.component'; 4 | 5 | describe('AddUserModalComponent', () => { 6 | let component: AddUserModalComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AddUserModalComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddUserModalComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/users/add-user-modal/add-user-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { BsModalRef } from 'ngx-bootstrap/modal'; 3 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 4 | import { IAddUserForm } from '../users.models'; 5 | 6 | // tslint:disable-next-line:max-line-length 7 | const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 8 | 9 | @Component({ 10 | selector: 'app-add-user-modal', 11 | templateUrl: './add-user-modal.component.html', 12 | styleUrls: ['./add-user-modal.component.scss'] 13 | }) 14 | export class AddUserModalComponent implements OnInit { 15 | addUserForm: FormGroup; 16 | userForm: IAddUserForm = { 17 | email: '', 18 | password: '' 19 | }; 20 | constructor(public bsModalRef: BsModalRef, private formBuilder: FormBuilder) { } 21 | 22 | ngOnInit() { 23 | // Set up the form values (you can add more later) 24 | this.addUserForm = this.formBuilder.group({ 25 | email: ['', [Validators.required, Validators.pattern(emailRegex)]], 26 | password: ['', [Validators.required]] 27 | }); 28 | } 29 | 30 | /** 31 | * @author Ahsan Ayaz 32 | * The function gets triggered on submission of the form when the user 33 | * clicks the save button. 34 | * The onFormSubmit function has to be bound by the parent using this modal 35 | * @param formValue 36 | */ 37 | submitForm(formValue) { 38 | this.bsModalRef.content.onFormSubmit(formValue); 39 | this.bsModalRef.hide(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/users/user-list-item/user-list-item.component.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 | 9 |
    10 | 11 | {{user?.location?.street}} 12 |
    13 | 14 | {{user.phone}} 15 |
    16 | 17 | {{user.email}} 18 |
    19 |
    20 |
    21 |
    x
    22 |
    23 |
  • -------------------------------------------------------------------------------- /src/app/users/user-list-item/user-list-item.component.scss: -------------------------------------------------------------------------------- 1 | .list-group-item{ 2 | position: relative; 3 | .delete-btn{ 4 | position: absolute; 5 | right: 10px; 6 | top: 10px; 7 | display: none; 8 | } 9 | &:hover{ 10 | background: #eee; 11 | transition: 1s ease all; 12 | 13 | .delete-btn{ 14 | cursor: pointer; 15 | display: block; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/users/user-list-item/user-list-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserListItemComponent } from './user-list-item.component'; 4 | 5 | describe('UserListItemComponent', () => { 6 | let component: UserListItemComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UserListItemComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UserListItemComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/users/user-list-item/user-list-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-user-list-item', 5 | templateUrl: './user-list-item.component.html', 6 | styleUrls: ['./user-list-item.component.scss'] 7 | }) 8 | export class UserListItemComponent implements OnInit { 9 | @Input() user; 10 | @Output() onUserDelete = new EventEmitter<{userId: 'string'}>(); 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | /** 17 | * @author Ahsan Ayaz 18 | * The function gets triggered when the user clicks the X button on a 19 | * user item under user lists. This emits an event upwards to parent component 20 | * with the user's id 21 | */ 22 | deleteUser() { 23 | this.onUserDelete.emit({ 24 | userId: this.user._id 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/app/users/users-list/users-list.component.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    -------------------------------------------------------------------------------- /src/app/users/users-list/users-list.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/app/users/users-list/users-list.component.scss -------------------------------------------------------------------------------- /src/app/users/users-list/users-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersListComponent } from './users-list.component'; 4 | 5 | describe('UsersListComponent', () => { 6 | let component: UsersListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UsersListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UsersListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/users/users-list/users-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-users-list', 5 | templateUrl: './users-list.component.html', 6 | styleUrls: ['./users-list.component.scss'] 7 | }) 8 | export class UsersListComponent implements OnInit { 9 | @Input() users; 10 | @Output() onUserDeleteEvent = new EventEmitter<{userId: string}>(); 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | /** 17 | * @author Ahsan Ayaz 18 | * The function gets triggered when a delete event is fired on a list item and navigates to this component 19 | * @param $event {userId: string} 20 | */ 21 | onUserDeleteHandler($event: {userId: string}) { 22 | this.onUserDeleteEvent.emit($event); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/users/users.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | 6 |
    7 | -------------------------------------------------------------------------------- /src/app/users/users.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | display: block; 3 | } 4 | 5 | #usersPage{ 6 | padding: 30px 20px; 7 | max-width: 500px; 8 | margin: 0 auto; 9 | 10 | .add-user-btn{ 11 | position: fixed; 12 | right: 50px; 13 | bottom: 50px; 14 | z-index: 2; 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/users/users.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersComponent } from './users.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: UsersComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UsersComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UsersComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/users/users.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { BsModalService } from 'ngx-bootstrap/modal'; 3 | import { ToastsManager } from 'ng2-toastr/ng2-toastr'; 4 | import 'rxjs/add/operator/first'; 5 | import { BsModalRef } from 'ngx-bootstrap/modal/modal-options.class'; 6 | import { AddUserModalComponent } from './add-user-modal/add-user-modal.component'; 7 | import { UsersService } from './users.service'; 8 | 9 | @Component({ 10 | selector: 'app-users', 11 | templateUrl: 'users.component.html', 12 | styleUrls: ['users.component.scss'] 13 | }) 14 | export class UsersComponent implements OnInit { 15 | usersList; 16 | addUserModalRef: BsModalRef; 17 | constructor( 18 | private modalService: BsModalService, 19 | private usersService: UsersService, 20 | private toastr: ToastsManager 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.usersService.getUsers() 25 | .first() 26 | .subscribe((response) => { 27 | this.usersList = response.users; 28 | }); 29 | } 30 | 31 | /** 32 | * @author Ahsan Ayaz 33 | * Shows the add user modal 34 | */ 35 | showAddUserModal() { 36 | this.addUserModalRef = this.modalService.show(AddUserModalComponent); 37 | this.addUserModalRef.content.onFormSubmit = this.onFormSubmit.bind(this); 38 | } 39 | 40 | /** 41 | * @author Ahsan Ayaz 42 | * This method is bound to the BsModalRef of the add user modal. 43 | * This gets triggered when the user submits that modal and the values from 44 | * the form inside that modal are received as parameters here. 45 | * Therefore, we can further send the data in addUser call using the service. 46 | */ 47 | onFormSubmit(createUserFormValue) { 48 | this.usersService.addUser(createUserFormValue) 49 | .first() 50 | .subscribe(res => { 51 | this.toastr.success('User added successfully'); 52 | this.usersList.unshift(res.user); 53 | }, err => { 54 | this.toastr.error('Could not add user'); 55 | }); 56 | } 57 | 58 | /** 59 | * @author Ahsan Ayaz 60 | * The function tirggers from the the child components and happens 61 | * when the user clicks the delete button on a user list item 62 | * @param $event 63 | */ 64 | deleteUser($event) { 65 | console.log($event); 66 | this.usersService.deleteUser($event) 67 | .first() 68 | .subscribe(res => { 69 | this.toastr.success('User deleted successfully'); 70 | this.usersList = this.usersList.filter(user => user._id !== $event.userId); 71 | }, err => { 72 | this.toastr.error('Could not delete user'); 73 | }); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/app/users/users.models.ts: -------------------------------------------------------------------------------- 1 | export interface IAddUserForm { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { ModalModule } from 'ngx-bootstrap/modal'; 5 | import { HttpModule } from '@angular/http'; 6 | import { UsersComponent } from './users.component'; 7 | import { RouterModule } from '@angular/router'; 8 | import { routes } from './users.routes'; 9 | import { UsersListComponent } from './users-list/users-list.component'; 10 | import { UserListItemComponent } from './user-list-item/user-list-item.component'; 11 | import { AddUserModalComponent } from './add-user-modal/add-user-modal.component'; 12 | import { UsersService } from './users.service'; 13 | 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule, 18 | RouterModule.forChild(routes), 19 | ModalModule.forRoot(), 20 | FormsModule, 21 | ReactiveFormsModule, 22 | HttpModule 23 | ], 24 | providers: [ UsersService ], 25 | entryComponents: [ AddUserModalComponent ], 26 | declarations: [ UsersComponent, UsersListComponent, UserListItemComponent, AddUserModalComponent ] 27 | }) 28 | export class UsersModule { } 29 | -------------------------------------------------------------------------------- /src/app/users/users.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | import { UsersComponent } from './users.component'; 3 | 4 | export const routes: Route[] = [ 5 | { 6 | path: '', 7 | component: UsersComponent 8 | } 9 | ]; 10 | -------------------------------------------------------------------------------- /src/app/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { UsersService } from './users.service'; 4 | 5 | describe('UsersService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [UsersService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([UsersService], (service: UsersService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import 'rxjs/add/observable/of'; 5 | import 'rxjs/add/operator/map'; 6 | 7 | @Injectable() 8 | export class UsersService { 9 | 10 | constructor(private http: Http) { } 11 | 12 | /** 13 | * @author Ahsan Ayaz 14 | * The function fetches the users from the node server 15 | */ 16 | getUsers() { 17 | return this.http.get('/api/v1/user') 18 | .map(res => res.json()); 19 | } 20 | 21 | /** 22 | * @author Ahsan Ayaz 23 | * The function sends a delete user request to the server based on user Id 24 | * @param params {userId for now} 25 | */ 26 | deleteUser(params) { 27 | return this.http.delete(`/api/v1/user/${params.userId}`) 28 | .map(res => res.json()); 29 | } 30 | 31 | /** 32 | * @author Ahsan Ayaz 33 | * Sends an add user call to server which in result adds a user in the database 34 | * @param params {email and password for now} 35 | */ 36 | addUser(params) { 37 | return this.http.post('/api/v1/user', params) 38 | .map(res => res.json()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/assets/angular.png -------------------------------------------------------------------------------- /src/assets/fastify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/assets/fastify.png -------------------------------------------------------------------------------- /src/assets/ng-fastify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/assets/ng-fastify.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhsanAyaz/angular-fastify-starter/26ac7a2385e3a5c01345bb30c5e8ecb1b79a45a1/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularFastifyStarter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2016", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | { 35 | "order": [ 36 | "static-field", 37 | "instance-field", 38 | "static-method", 39 | "instance-method" 40 | ] 41 | } 42 | ], 43 | "no-arg": true, 44 | "no-bitwise": true, 45 | "no-console": [ 46 | true, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-construct": true, 54 | "no-debugger": true, 55 | "no-duplicate-super": true, 56 | "no-empty": false, 57 | "no-empty-interface": true, 58 | "no-eval": true, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-misused-new": true, 64 | "no-non-null-assertion": true, 65 | "no-shadowed-variable": true, 66 | "no-string-literal": false, 67 | "no-string-throw": true, 68 | "no-switch-case-fall-through": true, 69 | "no-trailing-whitespace": true, 70 | "no-unnecessary-initializer": true, 71 | "no-unused-expression": true, 72 | "no-use-before-declare": true, 73 | "no-var-keyword": true, 74 | "object-literal-sort-keys": false, 75 | "one-line": [ 76 | true, 77 | "check-open-brace", 78 | "check-catch", 79 | "check-else", 80 | "check-whitespace" 81 | ], 82 | "prefer-const": true, 83 | "quotemark": [ 84 | true, 85 | "single" 86 | ], 87 | "radix": true, 88 | "semicolon": [ 89 | true, 90 | "always" 91 | ], 92 | "triple-equals": [ 93 | true, 94 | "allow-null-check" 95 | ], 96 | "typedef-whitespace": [ 97 | true, 98 | { 99 | "call-signature": "nospace", 100 | "index-signature": "nospace", 101 | "parameter": "nospace", 102 | "property-declaration": "nospace", 103 | "variable-declaration": "nospace" 104 | } 105 | ], 106 | "typeof-compare": true, 107 | "unified-signatures": true, 108 | "variable-name": false, 109 | "whitespace": [ 110 | true, 111 | "check-branch", 112 | "check-decl", 113 | "check-operator", 114 | "check-separator", 115 | "check-type" 116 | ], 117 | "directive-selector": [ 118 | true, 119 | "attribute", 120 | "app", 121 | "camelCase" 122 | ], 123 | "component-selector": [ 124 | true, 125 | "element", 126 | "app", 127 | "kebab-case" 128 | ], 129 | "use-input-property-decorator": true, 130 | "use-output-property-decorator": true, 131 | "use-host-property-decorator": true, 132 | "no-input-rename": true, 133 | "no-output-rename": true, 134 | "use-life-cycle-interface": true, 135 | "use-pipe-transform-interface": true, 136 | "component-class-suffix": true, 137 | "directive-class-suffix": true, 138 | "no-access-missing-member": true, 139 | "templates-use-public": true, 140 | "invoke-injectable": true 141 | } 142 | } 143 | --------------------------------------------------------------------------------