├── .gitignore
├── README.md
├── functions
├── todos-create.js
├── todos-delete-batch.js
├── todos-delete.js
├── todos-read-all.js
├── todos-read.js
├── todos-update.js
└── utils
│ └── getId.js
├── netlify.toml
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── scripts
├── bootstrap-fauna-database.js
└── check-for-fauna-key.js
├── src
├── App.css
├── App.js
├── App.test.js
├── assets
│ ├── deploy-to-netlify.svg
│ ├── github.svg
│ └── logo.svg
├── components
│ ├── AppHeader
│ │ ├── AppHeader.css
│ │ └── index.js
│ ├── ContentEditable
│ │ ├── ContentEditable.css
│ │ ├── Editable.js
│ │ └── index.js
│ ├── SettingsIcon
│ │ ├── SettingIcon.css
│ │ └── index.js
│ └── SettingsMenu
│ │ ├── SettingsMenu.css
│ │ └── index.js
├── index.css
├── index.js
└── utils
│ ├── api.js
│ ├── isLocalHost.js
│ └── sortByDate.js
└── webpack.config.js
/.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 | /functions-build
12 |
13 | # netlify
14 | .netlify
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > Moved to https://github.com/netlify/netlify-faunadb-example
2 |
3 | # Netlify + FaunaDB
4 |
5 | Example of using [FaunaDB](https://fauna.com/) with [Netlify functions](https://www.netlify.com/docs/functions/)
6 |
7 |
8 | - [About this application](#about-this-application)
9 | - [Setup & Run Locally](#setup--run-locally)
10 | - [TLDR; Quick Deploy](#tldr-quick-deploy)
11 | - [Tutorial](#tutorial)
12 | * [Background](#background)
13 | * [1. Create React app](#1-create-react-app)
14 | * [2. Create a function](#2-create-a-function)
15 | + [Anatomy of a Lambda function](#anatomy-of-a-lambda-function)
16 | + [Setting up functions for local development](#setting-up-functions-for-local-development)
17 | * [4. Connect the function to the frontend app](#4-connect-the-function-to-the-frontend-app)
18 | * [5. Finishing the Backend Functions](#5-finishing-the-backend-functions)
19 | * [Wrapping Up](#wrapping-up)
20 |
21 |
22 | ## About this application
23 |
24 | This application is using [React](https://reactjs.org/) for the frontend, [Netlify Functions](https://www.netlify.com/docs/functions/) for API calls, and [FaunaDB](https://fauna.com/) as the backing database.
25 |
26 | 
27 |
28 | ## Setup & Run Locally
29 |
30 | 1. Clone down the repository
31 |
32 | ```bash
33 | git clone git@github.com:netlify/netlify-faunadb-example.git
34 | ```
35 |
36 | 2. Install the dependencies
37 |
38 | ```bash
39 | npm install
40 | ```
41 |
42 | 3. Run project locally
43 |
44 | ```bash
45 | npm start
46 | ```
47 |
48 | ## TLDR; Quick Deploy
49 |
50 | 1. Click the [Deploy to Netlify Button](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/fauna-one-click&stack=fauna)
51 |
52 | [](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/fauna-one-click&stack=fauna)
53 |
54 | ## Tutorial
55 |
56 | ### Background
57 |
58 | This application is using [React](https://reactjs.org/) for the frontend, [Netlify Functions](https://www.netlify.com/docs/functions/) for API calls, and [FaunaDB](https://fauna.com/) as the backing database.
59 |
60 | We are going to explore how to get up and running with netlify functions and how to deploy your own serverless backend.
61 |
62 | So, lets dive right in!
63 |
64 | ### 1. Create React app
65 |
66 | We are using React for this demo app, but you can use whatever you want to manage the frontend.
67 |
68 | Into VueJS? Awesome use that.
69 |
70 | Miss the days of jQuery? Righto jQuery away!
71 |
72 | Fan of vanillaJS? By all means, have at it!
73 |
74 | 1. Install create react app
75 |
76 | ```bash
77 | npm install create-react-app -g
78 | ```
79 | 2. Create the react app!
80 |
81 | ```bash
82 | create-react-app my-app
83 | ```
84 |
85 | 3. The react app is now setup!
86 |
87 | ```bash
88 | # change directories into my-app
89 | cd my-app
90 | # start the app
91 | npm start
92 | ```
93 |
94 | ### 2. Create a function
95 |
96 | Now, lets create a function for our app and wire that up to run locally.
97 |
98 | The functions in our project are going to live in a `/functions` folder. You can set this to whatever you'd like but we like the `/functions` convention.
99 |
100 | #### Anatomy of a Lambda function
101 |
102 | All AWS Lambda functions have the following signature:
103 |
104 | ```js
105 | exports.handler = (event, context, callback) => {
106 | // "event" has informatiom about the path, body, headers etc of the request
107 | console.log('event', event)
108 | // "context" has information about the lambda environment and user details
109 | console.log('context', context)
110 | // The "callback" ends the execution of the function and returns a reponse back to the caller
111 | return callback(null, {
112 | statusCode: 200,
113 | body: JSON.stringify({
114 | data: '⊂◉‿◉つ'
115 | })
116 | })
117 | }
118 | ```
119 |
120 | We are going to use the `faunadb` npm package to connect to our Fauna Database and create an item
121 |
122 | #### Setting up functions for local development
123 |
124 | Lets rock and roll.
125 |
126 | 1. **Create a `./functions` directory**
127 |
128 | ```bash
129 | # make functions directory
130 | mdkir functions
131 | ```
132 |
133 | 2. **Install `netlify-lambda`**
134 |
135 | [Netlify lambda](https://github.com/netlify/netlify-lambda) is a tool for locally emulating the serverless function for development and for bundling our serverless function with third party npm modules (if we are using those)
136 |
137 | ```
138 | npm i netlify-lambda --save-dev
139 | ```
140 |
141 | To simulate our function endpoints locally, we need to setup a [proxy](https://github.com/netlify/create-react-app-lambda/blob/master/package.json#L19-L26) for webpack to use.
142 |
143 | **Note**: If you are using CreateReactApp v2 and above, follow the following _CRA v2 and above_ steps to add proxy settings, else continue to _CRA below v2_
144 |
145 | **CRA v2 and above**
146 | 1. Install http-proxy-middleware `npm i --save http-proxy-middleware`
147 | 2. Create a _setupProxy.js_ file inside your **src** folder
148 | 3. Put the following code inside `setupProxy.js`
149 | ```javascript
150 | const proxy = require('http-proxy-middleware');
151 |
152 | module.exports = function(app) {
153 | app.use(proxy('/.netlify/functions/', {
154 | target: 'http://localhost:9000/',
155 | "pathRewrite": {
156 | "^/\\.netlify/functions": ""
157 | }
158 | }));
159 | };
160 | ```
161 |
162 | **CRA below v2**
163 |
164 | In `package.json` add:
165 |
166 | ```json
167 | {
168 | "name": "react-lambda",
169 | ...
170 | "proxy": {
171 | "/.netlify/functions": {
172 | "target": "http://localhost:9000",
173 | "pathRewrite": {
174 | "^/\\.netlify/functions": ""
175 | }
176 | }
177 | }
178 | }
179 | ```
180 |
181 | This will proxy requests we make to `/.netlify/functions` to our locally running function server at port 9000.
182 |
183 | 3. **Add our `start` & `build` commands**
184 |
185 | Lets go ahead and add our `start` & `build` command to npm scripts in `package.json`. These will let us running things locally and give a command for netlify to run to build our app & functions when we are ready to deploy.
186 |
187 | We are going to be using the `npm-run-all` npm module to run our frontend & backend in parallel in the same terminal window.
188 |
189 | So install it!
190 |
191 | ```
192 | npm install npm-run-all --save-dev
193 | ```
194 |
195 | **About `npm start`**
196 |
197 | The `start:app` command will run `react-scripts start` to run our react app
198 |
199 | The `start:server` command will run `netlify-lambda serve functions -c ./webpack.config.js` to run our function code locally. The `-c webpack-config` flag lets us set a custom webpack config to [fix a module issue](https://medium.com/@danbruder/typeerror-require-is-not-a-function-webpack-faunadb-6e785858d23b) with faunaDB module.
200 |
201 | Running `npm start` in our terminal will run `npm-run-all --parallel start:app start:server` to fire them both up at once.
202 |
203 | **About `npm build`**
204 |
205 | The `build:app` command will run `react-scripts build` to run our react app
206 |
207 | The `build:server` command will run `netlify-lambda build functions -c ./webpack.config.js` to run our function code locally.
208 |
209 | Running `npm run build` in our terminal will run `npm-run-all --parallel build:**` to fire them both up at once.
210 |
211 |
212 | **Your `package.json` should look like**
213 |
214 | ```json
215 | {
216 | "name": "netlify-fauna",
217 | "scripts": {
218 | "👇 ABOUT-bootstrap-command": "💡 scaffold and setup FaunaDB #",
219 | "bootstrap": "node ./scripts/bootstrap-fauna-database.js",
220 | "👇 ABOUT-start-command": "💡 start the app and server #",
221 | "start": "npm-run-all --parallel start:app start:server",
222 | "start:app": "react-scripts start",
223 | "start:server": "netlify-lambda serve functions -c ./webpack.config.js",
224 | "👇 ABOUT-prebuild-command": "💡 before 'build' runs, run the 'bootstrap' command #",
225 | "prebuild": "echo 'setup faunaDB' && npm run bootstrap",
226 | "👇 ABOUT-build-command": "💡 build the react app and the serverless functions #",
227 | "build": "npm-run-all --parallel build:**",
228 | "build:app": "react-scripts build",
229 | "build:functions": "netlify-lambda build functions -c ./webpack.config.js",
230 | },
231 | "dependencies": {
232 | "faunadb": "^0.2.2",
233 | "react": "^16.4.0",
234 | "react-dom": "^16.4.0",
235 | "react-scripts": "1.1.4"
236 | },
237 | "devDependencies": {
238 | "netlify-lambda": "^0.4.0",
239 | "npm-run-all": "^4.1.3"
240 | },
241 | "proxy": {
242 | "/.netlify/functions": {
243 | "target": "http://localhost:9000",
244 | "pathRewrite": {
245 | "^/\\.netlify/functions": ""
246 | }
247 | }
248 | }
249 | }
250 |
251 | ```
252 |
253 | 4. **Install FaunaDB and write the create function**
254 |
255 | We are going to be using the `faunadb` npm module to call into our todos index in FaunaDB.
256 |
257 | So install it in the project
258 |
259 | ```bash
260 | npm i faunadb --save
261 | ```
262 |
263 | Then create a new function file in `/functions` called `todos-create.js`
264 |
265 |
266 |
267 | ```js
268 | /* code from functions/todos-create.js */
269 | import faunadb from 'faunadb' /* Import faunaDB sdk */
270 |
271 | /* configure faunaDB Client with our secret */
272 | const q = faunadb.query
273 | const client = new faunadb.Client({
274 | secret: process.env.FAUNADB_SERVER_SECRET
275 | })
276 |
277 | /* export our lambda function as named "handler" export */
278 | exports.handler = (event, context, callback) => {
279 | /* parse the string body into a useable JS object */
280 | const data = JSON.parse(event.body)
281 | console.log('Hello webinar. Function `todo-create` invoked', data)
282 | const todoItem = {
283 | data: data
284 | }
285 | /* construct the fauna query */
286 | return client.query(q.Create(q.Ref('classes/todos'), todoItem))
287 | .then((response) => {
288 | console.log('success', response)
289 | /* Success! return the response with statusCode 200 */
290 | return callback(null, {
291 | statusCode: 200,
292 | body: JSON.stringify(response)
293 | })
294 | }).catch((error) => {
295 | console.log('error', error)
296 | /* Error! return the error with statusCode 400 */
297 | return callback(null, {
298 | statusCode: 400,
299 | body: JSON.stringify(error)
300 | })
301 | })
302 | }
303 | ```
304 |
305 |
306 | ### 4. Connect the function to the frontend app
307 |
308 | Inside of the react app, we can now wire up the `/.netlify/functions/todos-create` endpoint to an AJAX request.
309 |
310 | ```js
311 | // Function using fetch to POST to our API endpoint
312 | function createTodo(data) {
313 | return fetch('/.netlify/functions/todos-create', {
314 | body: JSON.stringify(data),
315 | method: 'POST'
316 | }).then(response => {
317 | return response.json()
318 | })
319 | }
320 |
321 | // Todo data
322 | const myTodo = {
323 | title: 'My todo title',
324 | completed: false,
325 | }
326 |
327 | // create it!
328 | createTodo(myTodo).then((response) => {
329 | console.log('API response', response)
330 | // set app state
331 | }).catch((error) => {
332 | console.log('API error', error)
333 | })
334 | ```
335 |
336 | Requests to `/.netlify/function/[Function-File-Name]` will work seamlessly on localhost and on the live site because we are using the local proxy with webpack.
337 |
338 |
339 | We will be skipping over the rest of the frontend parts of the app because you can use whatever framework you'd like to build your application.
340 |
341 | All the demo React frontend code is [available here](https://github.com/netlify/netlify-faunadb-example/tree/17a9ba47a8b1b2408b68e793fba4c5fd17bf85da/src)
342 |
343 | ### 5. Finishing the Backend Functions
344 |
345 | So far we have created our `todo-create` function done and we've seen how we make requests to our live function endpoints. It's now time to add the rest of our CRUD functions to manage our todos.
346 |
347 | 1. **Read Todos by ID**
348 |
349 | Then create a new function file in `/functions` called `todos-read.js`
350 |
351 |
352 |
353 | ```js
354 | /* code from functions/todos-read.js */
355 | import faunadb from 'faunadb'
356 | import getId from './utils/getId'
357 |
358 | const q = faunadb.query
359 | const client = new faunadb.Client({
360 | secret: process.env.FAUNADB_SERVER_SECRET
361 | })
362 |
363 | exports.handler = (event, context, callback) => {
364 | const id = getId(event.path)
365 | console.log(`Function 'todo-read' invoked. Read id: ${id}`)
366 | return client.query(q.Get(q.Ref(`classes/todos/${id}`)))
367 | .then((response) => {
368 | console.log('success', response)
369 | return callback(null, {
370 | statusCode: 200,
371 | body: JSON.stringify(response)
372 | })
373 | }).catch((error) => {
374 | console.log('error', error)
375 | return callback(null, {
376 | statusCode: 400,
377 | body: JSON.stringify(error)
378 | })
379 | })
380 | }
381 | ```
382 |
383 |
384 | 2. **Read All Todos**
385 |
386 | Then create a new function file in `/functions` called `todos-read-all.js`
387 |
388 |
389 |
390 | ```js
391 | /* code from functions/todos-read-all.js */
392 | import faunadb from 'faunadb'
393 |
394 | const q = faunadb.query
395 | const client = new faunadb.Client({
396 | secret: process.env.FAUNADB_SERVER_SECRET
397 | })
398 |
399 | exports.handler = (event, context, callback) => {
400 | console.log('Function `todo-read-all` invoked')
401 | return client.query(q.Paginate(q.Match(q.Ref('indexes/all_todos'))))
402 | .then((response) => {
403 | const todoRefs = response.data
404 | console.log('Todo refs', todoRefs)
405 | console.log(`${todoRefs.length} todos found`)
406 | // create new query out of todo refs. http://bit.ly/2LG3MLg
407 | const getAllTodoDataQuery = todoRefs.map((ref) => {
408 | return q.Get(ref)
409 | })
410 | // then query the refs
411 | return client.query(getAllTodoDataQuery).then((ret) => {
412 | return callback(null, {
413 | statusCode: 200,
414 | body: JSON.stringify(ret)
415 | })
416 | })
417 | }).catch((error) => {
418 | console.log('error', error)
419 | return callback(null, {
420 | statusCode: 400,
421 | body: JSON.stringify(error)
422 | })
423 | })
424 | }
425 | ```
426 |
427 |
428 | 3. **Update todo by ID**
429 |
430 | Then create a new function file in `/functions` called `todos-update.js`
431 |
432 |
433 |
434 | ```js
435 | /* code from functions/todos-update.js */
436 | import faunadb from 'faunadb'
437 | import getId from './utils/getId'
438 |
439 | const q = faunadb.query
440 | const client = new faunadb.Client({
441 | secret: process.env.FAUNADB_SERVER_SECRET
442 | })
443 |
444 | exports.handler = (event, context, callback) => {
445 | const data = JSON.parse(event.body)
446 | const id = getId(event.path)
447 | console.log(`Function 'todo-update' invoked. update id: ${id}`)
448 | return client.query(q.Update(q.Ref(`classes/todos/${id}`), {data}))
449 | .then((response) => {
450 | console.log('success', response)
451 | return callback(null, {
452 | statusCode: 200,
453 | body: JSON.stringify(response)
454 | })
455 | }).catch((error) => {
456 | console.log('error', error)
457 | return callback(null, {
458 | statusCode: 400,
459 | body: JSON.stringify(error)
460 | })
461 | })
462 | }
463 | ```
464 |
465 |
466 |
467 | 4. **Delete by ID**
468 |
469 | Then create a new function file in `/functions` called `todos-delete.js`
470 |
471 |
472 |
473 | ```js
474 | /* code from functions/todos-delete.js */
475 | import faunadb from 'faunadb'
476 | import getId from './utils/getId'
477 |
478 | const q = faunadb.query
479 | const client = new faunadb.Client({
480 | secret: process.env.FAUNADB_SERVER_SECRET
481 | })
482 |
483 | exports.handler = (event, context, callback) => {
484 | const id = getId(event.path)
485 | console.log(`Function 'todo-delete' invoked. delete id: ${id}`)
486 | return client.query(q.Delete(q.Ref(`classes/todos/${id}`)))
487 | .then((response) => {
488 | console.log('success', response)
489 | return callback(null, {
490 | statusCode: 200,
491 | body: JSON.stringify(response)
492 | })
493 | }).catch((error) => {
494 | console.log('error', error)
495 | return callback(null, {
496 | statusCode: 400,
497 | body: JSON.stringify(error)
498 | })
499 | })
500 | }
501 | ```
502 |
503 |
504 |
505 |
506 | 4. **Delete batch todos**
507 |
508 | Then create a new function file in `/functions` called `todos-delete-batch.js`
509 |
510 |
511 |
512 | ```js
513 | /* code from functions/todos-delete-batch.js */
514 | import faunadb from 'faunadb'
515 | import getId from './utils/getId'
516 |
517 | const q = faunadb.query
518 | const client = new faunadb.Client({
519 | secret: process.env.FAUNADB_SERVER_SECRET
520 | })
521 |
522 | exports.handler = (event, context, callback) => {
523 | const data = JSON.parse(event.body)
524 | console.log('data', data)
525 | console.log('Function `todo-delete-batch` invoked', data.ids)
526 | // construct batch query from IDs
527 | const deleteAllCompletedTodoQuery = data.ids.map((id) => {
528 | return q.Delete(q.Ref(`classes/todos/${id}`))
529 | })
530 | // Hit fauna with the query to delete the completed items
531 | return client.query(deleteAllCompletedTodoQuery)
532 | .then((response) => {
533 | console.log('success', response)
534 | return callback(null, {
535 | statusCode: 200,
536 | body: JSON.stringify(response)
537 | })
538 | }).catch((error) => {
539 | console.log('error', error)
540 | return callback(null, {
541 | statusCode: 400,
542 | body: JSON.stringify(error)
543 | })
544 | })
545 | }
546 | ```
547 |
548 |
549 | After we deploy all these functions, we will be able to call them from our frontend code with these fetch calls:
550 |
551 |
552 |
553 | ```js
554 | /* Frontend code from src/utils/api.js */
555 | /* Api methods to call /functions */
556 |
557 | const create = (data) => {
558 | return fetch('/.netlify/functions/todos-create', {
559 | body: JSON.stringify(data),
560 | method: 'POST'
561 | }).then(response => {
562 | return response.json()
563 | })
564 | }
565 |
566 | const readAll = () => {
567 | return fetch('/.netlify/functions/todos-read-all').then((response) => {
568 | return response.json()
569 | })
570 | }
571 |
572 | const update = (todoId, data) => {
573 | return fetch(`/.netlify/functions/todos-update/${todoId}`, {
574 | body: JSON.stringify(data),
575 | method: 'POST'
576 | }).then(response => {
577 | return response.json()
578 | })
579 | }
580 |
581 | const deleteTodo = (todoId) => {
582 | return fetch(`/.netlify/functions/todos-delete/${todoId}`, {
583 | method: 'POST',
584 | }).then(response => {
585 | return response.json()
586 | })
587 | }
588 |
589 | const batchDeleteTodo = (todoIds) => {
590 | return fetch(`/.netlify/functions/todos-delete-batch`, {
591 | body: JSON.stringify({
592 | ids: todoIds
593 | }),
594 | method: 'POST'
595 | }).then(response => {
596 | return response.json()
597 | })
598 | }
599 |
600 | export default {
601 | create: create,
602 | readAll: readAll,
603 | update: update,
604 | delete: deleteTodo,
605 | batchDelete: batchDeleteTodo
606 | }
607 | ```
608 |
609 |
610 | ### Wrapping Up
611 |
612 | I hope you have enjoyed this tutorial on building your own CRUD API using Netlify serverless functions and FaunaDB.
613 |
614 | As you can see, functions can be extremely powerful when combined with a cloud database!
615 |
616 | The sky is the limit on what you can build with the JAM stack and we'd love to hear about what you make.
617 |
618 | **Next Steps**
619 |
620 | This example can be improved with users/authentication. Next steps to build out the app would be:
621 |
622 | - Add in the concept of users for everyone to have their own todo list
623 | - Wire up authentication using [Netlify Identity](https://identity.netlify.com/) JWTs
624 | - Add in due dates to todos and wire up Functions to notify users via email/SMS
625 | - File for IPO?
626 |
--------------------------------------------------------------------------------
/functions/todos-create.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb' /* Import faunaDB sdk */
2 |
3 | /* configure faunaDB Client with our secret */
4 | const q = faunadb.query
5 | const client = new faunadb.Client({
6 | secret: process.env.FAUNADB_SERVER_SECRET
7 | })
8 |
9 | /* export our lambda function as named "handler" export */
10 | exports.handler = (event, context, callback) => {
11 | /* parse the string body into a useable JS object */
12 | const data = JSON.parse(event.body)
13 | console.log('Hello webinar. Function `todo-create` invoked', data)
14 | const todoItem = {
15 | data: data
16 | }
17 | /* construct the fauna query */
18 | return client.query(q.Create(q.Ref('classes/todos'), todoItem))
19 | .then((response) => {
20 | console.log('success', response)
21 | /* Success! return the response with statusCode 200 */
22 | return callback(null, {
23 | statusCode: 200,
24 | body: JSON.stringify(response)
25 | })
26 | }).catch((error) => {
27 | console.log('error', error)
28 | /* Error! return the error with statusCode 400 */
29 | return callback(null, {
30 | statusCode: 400,
31 | body: JSON.stringify(error)
32 | })
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/functions/todos-delete-batch.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb'
2 | import getId from './utils/getId'
3 |
4 | const q = faunadb.query
5 | const client = new faunadb.Client({
6 | secret: process.env.FAUNADB_SERVER_SECRET
7 | })
8 |
9 | exports.handler = (event, context, callback) => {
10 | const data = JSON.parse(event.body)
11 | console.log('data', data)
12 | console.log('Function `todo-delete-batch` invoked', data.ids)
13 | // construct batch query from IDs
14 | const deleteAllCompletedTodoQuery = data.ids.map((id) => {
15 | return q.Delete(q.Ref(`classes/todos/${id}`))
16 | })
17 | // Hit fauna with the query to delete the completed items
18 | return client.query(deleteAllCompletedTodoQuery)
19 | .then((response) => {
20 | console.log('success', response)
21 | return callback(null, {
22 | statusCode: 200,
23 | body: JSON.stringify(response)
24 | })
25 | }).catch((error) => {
26 | console.log('error', error)
27 | return callback(null, {
28 | statusCode: 400,
29 | body: JSON.stringify(error)
30 | })
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/functions/todos-delete.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb'
2 | import getId from './utils/getId'
3 |
4 | const q = faunadb.query
5 | const client = new faunadb.Client({
6 | secret: process.env.FAUNADB_SERVER_SECRET
7 | })
8 |
9 | exports.handler = (event, context, callback) => {
10 | const id = getId(event.path)
11 | console.log(`Function 'todo-delete' invoked. delete id: ${id}`)
12 | return client.query(q.Delete(q.Ref(`classes/todos/${id}`)))
13 | .then((response) => {
14 | console.log('success', response)
15 | return callback(null, {
16 | statusCode: 200,
17 | body: JSON.stringify(response)
18 | })
19 | }).catch((error) => {
20 | console.log('error', error)
21 | return callback(null, {
22 | statusCode: 400,
23 | body: JSON.stringify(error)
24 | })
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/functions/todos-read-all.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb'
2 |
3 | const q = faunadb.query
4 | const client = new faunadb.Client({
5 | secret: process.env.FAUNADB_SERVER_SECRET
6 | })
7 |
8 | exports.handler = (event, context, callback) => {
9 | console.log('Function `todo-read-all` invoked')
10 | return client.query(q.Paginate(q.Match(q.Ref('indexes/all_todos'))))
11 | .then((response) => {
12 | const todoRefs = response.data
13 | console.log('Todo refs', todoRefs)
14 | console.log(`${todoRefs.length} todos found`)
15 | // create new query out of todo refs. http://bit.ly/2LG3MLg
16 | const getAllTodoDataQuery = todoRefs.map((ref) => {
17 | return q.Get(ref)
18 | })
19 | // then query the refs
20 | return client.query(getAllTodoDataQuery).then((ret) => {
21 | return callback(null, {
22 | statusCode: 200,
23 | body: JSON.stringify(ret)
24 | })
25 | })
26 | }).catch((error) => {
27 | console.log('error', error)
28 | return callback(null, {
29 | statusCode: 400,
30 | body: JSON.stringify(error)
31 | })
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/functions/todos-read.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb'
2 | import getId from './utils/getId'
3 |
4 | const q = faunadb.query
5 | const client = new faunadb.Client({
6 | secret: process.env.FAUNADB_SERVER_SECRET
7 | })
8 |
9 | exports.handler = (event, context, callback) => {
10 | const id = getId(event.path)
11 | console.log(`Function 'todo-read' invoked. Read id: ${id}`)
12 | return client.query(q.Get(q.Ref(`classes/todos/${id}`)))
13 | .then((response) => {
14 | console.log('success', response)
15 | return callback(null, {
16 | statusCode: 200,
17 | body: JSON.stringify(response)
18 | })
19 | }).catch((error) => {
20 | console.log('error', error)
21 | return callback(null, {
22 | statusCode: 400,
23 | body: JSON.stringify(error)
24 | })
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/functions/todos-update.js:
--------------------------------------------------------------------------------
1 | import faunadb from 'faunadb'
2 | import getId from './utils/getId'
3 |
4 | const q = faunadb.query
5 | const client = new faunadb.Client({
6 | secret: process.env.FAUNADB_SERVER_SECRET
7 | })
8 |
9 | exports.handler = (event, context, callback) => {
10 | const data = JSON.parse(event.body)
11 | const id = getId(event.path)
12 | console.log(`Function 'todo-update' invoked. update id: ${id}`)
13 | return client.query(q.Update(q.Ref(`classes/todos/${id}`), {data}))
14 | .then((response) => {
15 | console.log('success', response)
16 | return callback(null, {
17 | statusCode: 200,
18 | body: JSON.stringify(response)
19 | })
20 | }).catch((error) => {
21 | console.log('error', error)
22 | return callback(null, {
23 | statusCode: 400,
24 | body: JSON.stringify(error)
25 | })
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/functions/utils/getId.js:
--------------------------------------------------------------------------------
1 |
2 | export default function getId(urlPath) {
3 | return urlPath.match(/([^\/]*)\/*$/)[0]
4 | }
5 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | functions = "functions-build"
3 | # This will be run the site build
4 | command = "npm run build"
5 | # This is the directory is publishing to netlify's CDN
6 | publish = "build"
7 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "netlify-fauna",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "faunadb": "^0.2.2",
7 | "react": "^16.4.0",
8 | "react-dom": "^16.4.0",
9 | "react-scripts": "1.1.4"
10 | },
11 | "scripts": {
12 | "bootstrap": "node ./scripts/bootstrap-fauna-database.js",
13 | "docs": "md-magic --path '**/*.md' --ignore 'node_modules'",
14 | "checkForFaunaKey": "node ./scripts/check-for-fauna-key.js",
15 | "start": "npm-run-all --parallel checkForFaunaKey start:app start:server",
16 | "start:app": "react-scripts start",
17 | "start:server": "netlify-lambda serve functions -c ./webpack.config.js",
18 | "prebuild": "echo 'setup faunaDB' && npm run bootstrap",
19 | "build": "npm-run-all --parallel build:**",
20 | "build:app": "react-scripts build",
21 | "build:functions": "netlify-lambda build functions -c ./webpack.config.js",
22 | "test": "react-scripts test --env=jsdom"
23 | },
24 | "devDependencies": {
25 | "markdown-magic": "^0.1.23",
26 | "netlify-lambda": "^0.4.0",
27 | "npm-run-all": "^4.1.3"
28 | },
29 | "proxy": {
30 | "/.netlify/functions": {
31 | "target": "http://localhost:9000",
32 | "pathRewrite": {
33 | "^/\\.netlify/functions": ""
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netlify/fauna-one-click/2a9042acf8893c74bc9f59b6990f64eb95cc4198/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 | Using FaunaDB & Netlify functions 18 |
19 |