├── public ├── favicon.ico ├── manifest.json └── index.html ├── netlify.toml ├── src ├── App.test.js ├── lambda │ ├── hello.js │ └── async-dadjoke.js ├── index.css ├── index.js ├── App.css ├── App.js ├── logo.svg └── serviceWorker.js ├── .gitignore ├── package.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VitorGuedesMadeira/Bookstore-React-Netlify/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn build" # the command you run to build this file 3 | functions = "built-lambda" # netlify-lambda builds to this folder AND Netlify reads functions from here 4 | publish = "build" # create-react-app builds to this folder, Netlify should serve all these files statically -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/lambda/hello.js: -------------------------------------------------------------------------------- 1 | // this uses the callback syntax, however, we encourage you to try the async/await syntax shown in async-dadjoke.js 2 | export function handler(event, context, callback) { 3 | console.log('queryStringParameters', event.queryStringParameters) 4 | callback(null, { 5 | statusCode: 200, 6 | body: JSON.stringify({ msg: 'Hello, World!' }), 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /built-lambda 12 | 13 | /.netlify 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lambda/async-dadjoke.js: -------------------------------------------------------------------------------- 1 | // example of async handler using async-await 2 | // https://github.com/netlify/netlify-lambda/issues/43#issuecomment-444618311 3 | 4 | import axios from "axios" 5 | export async function handler(event, context) { 6 | try { 7 | const response = await axios.get("https://icanhazdadjoke.com", { headers: { Accept: "application/json" } }) 8 | const data = response.data 9 | return { 10 | statusCode: 200, 11 | body: JSON.stringify({ msg: data.joke }) 12 | } 13 | } catch (err) { 14 | console.log(err) // output to netlify function log 15 | return { 16 | statusCode: 500, 17 | body: JSON.stringify({ msg: err.message }) // Could be a custom message or object i.e. JSON.stringify(err) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app-lambda", 3 | "version": "0.5.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.19.0", 7 | "react": "^16.8.6", 8 | "react-dom": "^16.8.6", 9 | "react-scripts": "^3.0.1" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "start:lambda": "netlify-lambda serve src/lambda", 14 | "build": "run-p build:**", 15 | "build:app": "react-scripts build", 16 | "build:lambda": "netlify-lambda build src/lambda", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ], 29 | "devDependencies": { 30 | "netlify-lambda": "^1.4.5", 31 | "npm-run-all": "^4.1.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import logo from "./logo.svg" 3 | import "./App.css" 4 | 5 | class LambdaDemo extends Component { 6 | constructor(props) { 7 | super(props) 8 | this.state = { loading: false, msg: null } 9 | } 10 | 11 | handleClick = api => e => { 12 | e.preventDefault() 13 | 14 | this.setState({ loading: true }) 15 | fetch("/.netlify/functions/" + api) 16 | .then(response => response.json()) 17 | .then(json => this.setState({ loading: false, msg: json.msg })) 18 | } 19 | 20 | render() { 21 | const { loading, msg } = this.state 22 | 23 | return ( 24 |

25 | 26 | 27 |
28 | {msg} 29 |

30 | ) 31 | } 32 | } 33 | 34 | class App extends Component { 35 | render() { 36 | return ( 37 |
38 |
39 | logo 40 |

41 | Edit src/App.js and save to reload. 42 |

43 | 44 |
45 |
46 | ) 47 | } 48 | } 49 | 50 | export default App 51 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export function register(config) { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Let's check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl, config); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl, config); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl, config) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | 70 | // Execute callback 71 | if (config && config.onUpdate) { 72 | config.onUpdate(registration); 73 | } 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // "Content is cached for offline use." message. 78 | console.log('Content is cached for offline use.'); 79 | 80 | // Execute callback 81 | if (config && config.onSuccess) { 82 | config.onSuccess(registration); 83 | } 84 | } 85 | } 86 | }; 87 | }; 88 | }) 89 | .catch(error => { 90 | console.error('Error during service worker registration:', error); 91 | }); 92 | } 93 | 94 | function checkValidServiceWorker(swUrl, config) { 95 | // Check if the service worker can be found. If it can't reload the page. 96 | fetch(swUrl) 97 | .then(response => { 98 | // Ensure service worker exists, and that we really are getting a JS file. 99 | if ( 100 | response.status === 404 || 101 | response.headers.get('content-type').indexOf('javascript') === -1 102 | ) { 103 | // No service worker found. Probably a different app. Reload the page. 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister().then(() => { 106 | window.location.reload(); 107 | }); 108 | }); 109 | } else { 110 | // Service worker found. Proceed as normal. 111 | registerValidSW(swUrl, config); 112 | } 113 | }) 114 | .catch(() => { 115 | console.log( 116 | 'No internet connection found. App is running in offline mode.' 117 | ); 118 | }); 119 | } 120 | 121 | export function unregister() { 122 | if ('serviceWorker' in navigator) { 123 | navigator.serviceWorker.ready.then(registration => { 124 | registration.unregister(); 125 | }); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Create-React-App-Lambda 2 | 3 | This project is a reference demo showing you how to use [Create React App v3](https://github.com/facebookincubator/create-react-app) and [netlify-lambda v1](https://github.com/netlify/netlify-lambda) together in a [Netlify Dev](https://www.netlify.com/docs/cli/?utm_source=github&utm_medium=swyx-CRAL&utm_campaign=devex#netlify-dev-beta) workflow. You can clone this and immediately be productive with a React app with serverless Netlify Functions in the same repo. Alternatively you can deploy straight to Netlify with this one-click Deploy: 4 | 5 | 6 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg?utm_source=github&utm_medium=swyx-CRAL&utm_campaign=devex)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/create-react-app-lambda&utm_source=github&utm_medium=swyx-CRAL&utm_campaign=devex) 7 | 8 | > ⚠️NOTE: You may not need this project at all. [Netlify Dev](https://github.com/netlify/netlify-dev-plugin) works with `create-react-app` out of the box! Only use `netlify-lambda` if you need a build step for your functions, eg if you want to use Babel or TypeScript ([see its README for details](https://github.com/netlify/netlify-lambda/blob/master/README.md#netlify-lambda)). 9 | 10 | ## Project Setup 11 | 12 | **Source**: The main addition to base Create-React-App is a new folder: `src/lambda`. This folder is specified and can be changed in the `package.json` script: `"build:lambda": "netlify-lambda build src/lambda"`. 13 | 14 | **Dist**: Each JavaScript file in there will be built for Netlify Function deployment in `/built-lambda`, specified in [`netlify.toml`](https://www.netlify.com/docs/netlify-toml-reference/?utm_source=github&utm_medium=swyx-CRAL&utm_campaign=devex). 15 | 16 | As an example, we've included a small `src/lambda/hello.js` function, which will be deployed to `/.netlify/functions/hello`. We've also included an async lambda example using async/await syntax in `async-dadjoke.js`. 17 | 18 | ## Video 19 | 20 | Learn how to set this up yourself (and why everything is the way it is) from scratch in a video: https://www.youtube.com/watch?v=3ldSM98nCHI 21 | 22 | ## Babel/webpack compilation 23 | 24 | All functions (inside `src/lambda`) are compiled with webpack using Babel, so you can use modern JavaScript, import npm modules, etc., without any extra setup. 25 | 26 | ## Local Development 27 | 28 | ```bash 29 | ## prep steps for first time users 30 | npm i -g netlify-cli # Make sure you have the [Netlify CLI](https://github.com/netlify/cli) installed 31 | git clone https://github.com/netlify/create-react-app-lambda ## clone this repo 32 | cd create-react-app-lambda ## change into this repo 33 | yarn # install all dependencies 34 | 35 | ## done every time you start up this project 36 | ntl dev ## nice shortcut for `netlify dev`, starts up create-react-app AND a local Node.js server for your Netlify functions 37 | ``` 38 | 39 | This fires up [Netlify Dev](https://www.netlify.com/docs/cli/?utm_source=github&utm_medium=swyx-CRAL&utm_campaign=devex#netlify-dev-beta), which: 40 | 41 | - Detects that you are running a `create-react-app` project and runs the npm script that contains `react-scripts start`, which in this project is the `start` script 42 | - Detects that you use `netlify-lambda` as a [function builder](https://github.com/netlify/netlify-dev-plugin/#function-builders-function-builder-detection-and-relationship-with-netlify-lambda), and runs the npm script that contains `netlify-lambda build`, which in this project is the `build:lambda` script. 43 | 44 | You can view the project locally via Netlify Dev, via `localhost:8888`. 45 | 46 | Each function will be available at the same port as well: 47 | 48 | - `http://localhost:8888/.netlify/functions/hello` and 49 | - `http://localhost:8888/.netlify/functions/async-dadjoke` 50 | 51 | ## Deployment 52 | 53 | During deployment, this project is configured, inside `netlify.toml` to run the build `command`: `yarn build`. 54 | 55 | `yarn build` corresponds to the npm script `build`, which uses `npm-run-all` (aka `run-p`) to concurrently run `"build:app"` (aka `react-scripts build`) and `build:lambda` (aka `netlify-lambda build src/lambda`). 56 | 57 | ## Typescript 58 | 59 |
60 | 61 | Click for instructions 62 | 63 | 64 | You can use Typescript in both your frontend React code (with `react-scripts` v2.1+) and your serverless functions (with `netlify-lambda` v1.1+). Follow these instructions: 65 | 66 | 1. `yarn add -D typescript @types/node @types/react @types/react-dom @babel/preset-typescript @types/aws-lambda` 67 | 2. convert `src/lambda/hello.js` to `src/lambda/hello.ts` 68 | 3. use types in your event handler: 69 | 70 | ```ts 71 | import { Handler, Context, Callback, APIGatewayEvent } from 'aws-lambda' 72 | 73 | interface HelloResponse { 74 | statusCode: number 75 | body: string 76 | } 77 | 78 | const handler: Handler = (event: APIGatewayEvent, context: Context, callback: Callback) => { 79 | const params = event.queryStringParameters 80 | const response: HelloResponse = { 81 | statusCode: 200, 82 | body: JSON.stringify({ 83 | msg: `Hello world ${Math.floor(Math.random() * 10)}`, 84 | params, 85 | }), 86 | } 87 | 88 | callback(undefined, response) 89 | } 90 | 91 | export { handler } 92 | ``` 93 | 94 | rerun and see it work! 95 | 96 | You are free to set up your `tsconfig.json` and `tslint` as you see fit. 97 | 98 |
99 | 100 | **If you want to try working in Typescript on the client and lambda side**: There are a bunch of small setup details to get right. Check https://github.com/sw-yx/create-react-app-lambda-typescript for a working starter. 101 | 102 | ## Routing and authentication with Netlify Identity 103 | 104 | For a full demo of routing and authentication, check this branch: https://github.com/netlify/create-react-app-lambda/pull/18 This example will not be maintained but may be helpful. 105 | 106 | ## Service Worker 107 | 108 | `create-react-app`'s default service worker (in `src/index.js`) does not work with lambda functions out of the box. It prevents calling the function and returns the app itself instead ([Read more](https://github.com/facebook/create-react-app/issues/2237#issuecomment-302693219)). To solve this you have to eject and enhance the service worker configuration in the webpack config. Whitelist the path of your lambda function and you are good to go. 109 | --------------------------------------------------------------------------------