Skip to content

Commit

Permalink
add mock api
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamed authored and mohamed committed May 16, 2020
1 parent c3f5d91 commit 053a1e1
Show file tree
Hide file tree
Showing 10 changed files with 949 additions and 30 deletions.
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
"i18n"
],
"scripts": {
"start": "HOST=localhost react-scripts start",
"start": "run-p start:dev start:api",
"start:dev": "cross-env REACT_APP_API_URL=http://localhost:3001 HOST=localhost react-scripts start",
"prestart:api": "ts-node tools/createMockDb.ts",
"start:api": "ts-node tools/apiServer.ts",
"build": "CI=true react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
Expand Down Expand Up @@ -57,8 +60,12 @@
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-sidebar": "^3.0.0",
"cross-env": "^7.0.2",
"json-server": "^0.16.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"react-test-renderer": "^16.13.1",
"ts-node": "^8.10.1",
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
"tslint-react": "^5.0.0",
Expand Down
17 changes: 17 additions & 0 deletions src/api/apiUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export async function handleResponse(response) {
if (response.ok) return response.json();
if (response.status === 400) {
// So, a server-side validation error occurred.
// Server side validation returns a string error message, so parse as text instead of json.
const error = await response.text();
throw new Error(error);
}
throw new Error("Network response was not ok.");
}

// In a real app, would likely call an error logging service.
export function handleError(error) {
// eslint-disable-next-line no-console
console.error("API call failed. " + error);
throw error;
}
24 changes: 24 additions & 0 deletions src/api/authorApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { handleResponse, handleError } from './apiUtils';
const baseUrl = process.env.REACT_APP_API_URL + '/authors/';

export function getAuthors() {
return fetch(baseUrl)
.then(handleResponse)
.catch(handleError);
}

export function saveAuthor(author) {
return fetch(baseUrl + (author.id || ''), {
method: author.id ? 'PUT' : 'POST', // POST for create, PUT to update when id already exists.
headers: { 'content-type': 'application/json' },
body: JSON.stringify(author),
})
.then(handleResponse)
.catch(handleError);
}

export function deleteAuthor(authorId) {
return fetch(baseUrl + authorId, { method: 'DELETE' })
.then(handleResponse)
.catch(handleError);
}
42 changes: 42 additions & 0 deletions src/api/courseApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { handleResponse, handleError } from './apiUtils';
const baseUrl = process.env.REACT_APP_API_URL + '/courses/';

export function getCourses() {
return fetch(baseUrl).then(handleResponse).catch(handleError);
}

export function getCourseBySlug(slug) {
return fetch(baseUrl + '?slug=' + slug)
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok.');
}
return response.json().then((courses) => {
if (courses.length !== 1) {
throw new Error('Course not found: ' + slug);
}
return courses[0]; // should only find one course for a given slug, so return it.
});
})
.catch(handleError);
}

export function saveCourse(course) {
return fetch(baseUrl + (course.id || ''), {
method: course.id ? 'PUT' : 'POST', // POST for create, PUT to update when id already exists.
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
...course,
// Parse authorId to a number (in case it was sent as a string).
authorId: parseInt(course.authorId, 10),
}),
})
.then(handleResponse)
.catch(handleError);
}

export function deleteCourse(courseId) {
return fetch(baseUrl + courseId, { method: 'DELETE' })
.then(handleResponse)
.catch(handleError);
}
89 changes: 89 additions & 0 deletions tools/apiServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
This uses json-server, but with the module approach: https://github.com/typicode/json-server#module
Downside: You can't pass the json-server command line options.
Instead, can override some defaults by passing a config object to jsonServer.defaults();
You have to check the source code to set some items.
Examples:
Validation/Customization: https://github.com/typicode/json-server/issues/266
Delay: https://github.com/typicode/json-server/issues/534
ID: https://github.com/typicode/json-server/issues/613#issuecomment-325393041
Relevant source code: https://github.com/typicode/json-server/blob/master/src/cli/run.js
*/

/* eslint-disable no-console */
const jsonServer = require('json-server');
const server = jsonServer.create();
const path = require('path');
const router = jsonServer.router(path.join(__dirname, 'db.json'));

// Can pass a limited number of options to this to override (some) defaults. See https://github.com/typicode/json-server#api
const middlewares = jsonServer.defaults({
// Display json-server's built in homepage when json-server starts.
static: 'node_modules/json-server/dist',
});

// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);

// To handle POST, PUT and PATCH you need to use a body-parser. Using JSON Server's bodyParser
server.use(jsonServer.bodyParser);

// Simulate delay on all requests
server.use(function (req, res, next) {
setTimeout(next, 0);
});

// Declaring custom routes below. Add custom routes before JSON Server router

// Add createdAt to all POSTS
server.use((req, res, next) => {
if (req.method === 'POST') {
req.body.createdAt = Date.now();
}
// Continue to JSON Server router
next();
});

server.post('/courses/', function (req, res, next) {
const error = validateCourse(req.body);
if (error) {
res.status(400).send(error);
} else {
req.body.slug = createSlug(req.body.title); // Generate a slug for new courses.
next();
}
});

// Use default router
server.use(router);

// Start server
const port = 3001;
server.listen(port, () => {
console.log(`JSON Server is running on port ${port}`);
});

// Centralized logic

// Returns a URL friendly slug
function createSlug(value) {
return value
.replace(/[^a-z0-9_]+/gi, '-')
.replace(/^-|-$/g, '')
.toLowerCase();
}

function validateCourse(course) {
if (!course.title) {
return 'Title is required.';
}
if (!course.authorId) {
return 'Author is required.';
}
if (!course.category) {
return 'Category is required.';
}
return '';
}

export {};
15 changes: 15 additions & 0 deletions tools/createMockDb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// import * as mockData from './mockData';
const fs = require('fs');
const path = require('path');
const mockData = require('./mockData');

const { courses, authors } = mockData;

const data = JSON.stringify({ courses, authors });
const filepath = path.join(__dirname, 'db.json');

fs.writeFile(filepath, data, function (err) {
err ? console.log(err) : console.log('Mock DB created.');
});

export {};
1 change: 1 addition & 0 deletions tools/db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"courses":[{"id":1,"title":"Securing React Apps with Auth0","slug":"react-auth0-authentication-security","authorId":1,"category":"JavaScript"},{"id":2,"title":"React: The Big Picture","slug":"react-big-picture","authorId":1,"category":"JavaScript"},{"id":3,"title":"Creating Reusable React Components","slug":"react-creating-reusable-components","authorId":1,"category":"JavaScript"},{"id":4,"title":"Building a JavaScript Development Environment","slug":"javascript-development-environment","authorId":1,"category":"JavaScript"},{"id":5,"title":"Building Applications with React and Redux","slug":"react-redux-react-router-es6","authorId":1,"category":"JavaScript"},{"id":6,"title":"Building Applications in React and Flux","slug":"react-flux-building-applications","authorId":1,"category":"JavaScript"},{"id":7,"title":"Clean Code: Writing Code for Humans","slug":"writing-clean-code-humans","authorId":1,"category":"Software Practices"},{"id":8,"title":"Architecting Applications for the Real World","slug":"architecting-applications-dotnet","authorId":1,"category":"Software Architecture"},{"id":9,"title":"Becoming an Outlier: Reprogramming the Developer Mind","slug":"career-reboot-for-developer-mind","authorId":1,"category":"Career"},{"id":10,"title":"Web Component Fundamentals","slug":"web-components-shadow-dom","authorId":1,"category":"HTML5"}],"authors":[{"id":1,"name":"Cory House"},{"id":2,"name":"Scott Allen"},{"id":3,"name":"Dan Wahlin"}]}
93 changes: 93 additions & 0 deletions tools/mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const courses = [
{
id: 1,
title: 'Securing React Apps with Auth0',
slug: 'react-auth0-authentication-security',
authorId: 1,
category: 'JavaScript',
},
{
id: 2,
title: 'React: The Big Picture',
slug: 'react-big-picture',
authorId: 1,
category: 'JavaScript',
},
{
id: 3,
title: 'Creating Reusable React Components',
slug: 'react-creating-reusable-components',
authorId: 1,
category: 'JavaScript',
},
{
id: 4,
title: 'Building a JavaScript Development Environment',
slug: 'javascript-development-environment',
authorId: 1,
category: 'JavaScript',
},
{
id: 5,
title: 'Building Applications with React and Redux',
slug: 'react-redux-react-router-es6',
authorId: 1,
category: 'JavaScript',
},
{
id: 6,
title: 'Building Applications in React and Flux',
slug: 'react-flux-building-applications',
authorId: 1,
category: 'JavaScript',
},
{
id: 7,
title: 'Clean Code: Writing Code for Humans',
slug: 'writing-clean-code-humans',
authorId: 1,
category: 'Software Practices',
},
{
id: 8,
title: 'Architecting Applications for the Real World',
slug: 'architecting-applications-dotnet',
authorId: 1,
category: 'Software Architecture',
},
{
id: 9,
title: 'Becoming an Outlier: Reprogramming the Developer Mind',
slug: 'career-reboot-for-developer-mind',
authorId: 1,
category: 'Career',
},
{
id: 10,
title: 'Web Component Fundamentals',
slug: 'web-components-shadow-dom',
authorId: 1,
category: 'HTML5',
},
];

const authors = [
{ id: 1, name: 'Cory House' },
{ id: 2, name: 'Scott Allen' },
{ id: 3, name: 'Dan Wahlin' },
];

const newCourse = {
id: null,
title: '',
authorId: null,
category: '',
};

module.exports = {
newCourse,
courses,
authors,
};

export {};
12 changes: 5 additions & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"plugins": [
{
"name": "typescript-tslint-plugin"
}
],
"plugins": [{
"name": "typescript-tslint-plugin"
}],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
Expand All @@ -22,4 +20,4 @@
"noImplicitAny": false
},
"include": ["src"]
}
}
Loading

0 comments on commit 053a1e1

Please sign in to comment.