Thanksgiving is about to arrive in the US and, as every year, millions of people and I are preparing our shopping lists for Thanksgiving dinner. I thought this is a good opportunity to flex my coding muscles again and see how quickly I can build an online shopping list using JavaScript. To my surprise, it was easier and quicker than I originally thought and perhaps you like a little challenge yourself. Here is how it went…
Obviously, there are already many shopping and todo list apps out there and, of course, there is also still good old pen and paper. However, I was curious how much it would actually take to build a quick shopping list MVP (minimum viable product) webpage myself that I can then share with my wife over the web. Not wanting to reinvent the wheel from scratch, I remembered that there was the awesome TodoMVC project out there, originally designed to help you select an MV* framework, but with a few tweaks, I figured I could probably leverage one of these examples for my purposes.
In the end, I decided to reuse the React example, not because I thought React is better than any other framework or anything like that, but just because I felt it was in line with my rusty JavaScript skills and will allow me to get going quickly. Of course, any other framework will do as well, so feel free to pick and choose whatever you prefer and feel comfortable with!
Getting the app up and running
The beauty of the TodoMVC examples is that they are super simple to get them up and running. For the particular one I chose, all I had to do was to make the example directory available to some web server. I believe that’s the very same for any other example on that website as well. As for the web server, I decided to go for Nginx, but of course, any web server will do. Installing nginx is as simple as a sudo dnf -y install nginx
on Oracle Linux 8:
[opc@thanksgiving-shopping-list ~]$ sudo dnf -y install nginx
Last metadata expiration check: 0:08:29 ago on Fri 20 Nov 2020 07:14:39 PM GMT.
Dependencies resolved.
======================================================================================================================================================================
Package Architecture Version Repository Size
======================================================================================================================================================================
Installing:
nginx x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 569 k
Installing dependencies:
gd x86_64 2.2.5-7.el8 ol8_appstream 144 k
jbigkit-libs x86_64 2.1-14.el8 ol8_appstream 55 k
libXpm x86_64 3.5.12-8.el8 ol8_appstream 58 k
libjpeg-turbo x86_64 1.5.3-10.el8 ol8_appstream 155 k
libtiff x86_64 4.0.9-18.el8 ol8_appstream 188 k
libwebp x86_64 1.0.0-1.el8 ol8_appstream 273 k
libxslt x86_64 1.1.32-5.0.1.el8 ol8_baseos_latest 250 k
nginx-all-modules noarch 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 24 k
nginx-filesystem noarch 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 25 k
nginx-mod-http-image-filter x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 35 k
nginx-mod-http-perl x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 46 k
nginx-mod-http-xslt-filter x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 34 k
nginx-mod-mail x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 64 k
nginx-mod-stream x86_64 1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e ol8_appstream 86 k
Transaction Summary
======================================================================================================================================================================
Install 15 Packages
Total download size: 2.0 M
Installed size: 5.3 M
Downloading Packages:
(1/15): libxslt-1.1.32-5.0.1.el8.x86_64.rpm 11 MB/s | 250 kB 00:00
(2/15): jbigkit-libs-2.1-14.el8.x86_64.rpm 2.3 MB/s | 55 kB 00:00
(3/15): gd-2.2.5-7.el8.x86_64.rpm 5.9 MB/s | 144 kB 00:00
(4/15): libXpm-3.5.12-8.el8.x86_64.rpm 48 MB/s | 58 kB 00:00
(5/15): libjpeg-turbo-1.5.3-10.el8.x86_64.rpm 90 MB/s | 155 kB 00:00
(6/15): libtiff-4.0.9-18.el8.x86_64.rpm 83 MB/s | 188 kB 00:00
(7/15): libwebp-1.0.0-1.el8.x86_64.rpm 94 MB/s | 273 kB 00:00
(8/15): nginx-all-modules-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch.rpm 19 MB/s | 24 kB 00:00
(9/15): nginx-filesystem-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch.rpm 24 MB/s | 25 kB 00:00
(10/15): nginx-mod-http-image-filter-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 34 MB/s | 35 kB 00:00
(11/15): nginx-mod-http-perl-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 45 MB/s | 46 kB 00:00
(12/15): nginx-mod-http-xslt-filter-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 33 MB/s | 34 kB 00:00
(13/15): nginx-mod-mail-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 63 MB/s | 64 kB 00:00
(14/15): nginx-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 97 MB/s | 569 kB 00:00
(15/15): nginx-mod-stream-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm 50 MB/s | 86 kB 00:00
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total 56 MB/s | 2.0 MB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : libjpeg-turbo-1.5.3-10.el8.x86_64 1/15
Running scriptlet: nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 2/15
Installing : nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 2/15
Installing : libwebp-1.0.0-1.el8.x86_64 3/15
Installing : libXpm-3.5.12-8.el8.x86_64 4/15
Installing : jbigkit-libs-2.1-14.el8.x86_64 5/15
Running scriptlet: jbigkit-libs-2.1-14.el8.x86_64 5/15
Installing : libtiff-4.0.9-18.el8.x86_64 6/15
Installing : gd-2.2.5-7.el8.x86_64 7/15
Running scriptlet: gd-2.2.5-7.el8.x86_64 7/15
Installing : libxslt-1.1.32-5.0.1.el8.x86_64 8/15
Installing : nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 9/15
Running scriptlet: nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 9/15
Installing : nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 10/15
Running scriptlet: nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 10/15
Installing : nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 11/15
Running scriptlet: nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 11/15
Installing : nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 12/15
Running scriptlet: nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 12/15
Installing : nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 13/15
Running scriptlet: nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 13/15
Installing : nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 14/15
Running scriptlet: nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 14/15
Installing : nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 15/15
Running scriptlet: nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 15/15
Verifying : libxslt-1.1.32-5.0.1.el8.x86_64 1/15
Verifying : gd-2.2.5-7.el8.x86_64 2/15
Verifying : jbigkit-libs-2.1-14.el8.x86_64 3/15
Verifying : libXpm-3.5.12-8.el8.x86_64 4/15
Verifying : libjpeg-turbo-1.5.3-10.el8.x86_64 5/15
Verifying : libtiff-4.0.9-18.el8.x86_64 6/15
Verifying : libwebp-1.0.0-1.el8.x86_64 7/15
Verifying : nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 8/15
Verifying : nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 9/15
Verifying : nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch 10/15
Verifying : nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 11/15
Verifying : nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 12/15
Verifying : nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 13/15
Verifying : nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 14/15
Verifying : nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 15/15
Installed:
gd-2.2.5-7.el8.x86_64 jbigkit-libs-2.1-14.el8.x86_64
libXpm-3.5.12-8.el8.x86_64 libjpeg-turbo-1.5.3-10.el8.x86_64
libtiff-4.0.9-18.el8.x86_64 libwebp-1.0.0-1.el8.x86_64
libxslt-1.1.32-5.0.1.el8.x86_64 nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch
nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64 nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
Complete!
[opc@thanksgiving-shopping-list ~]$ sudo systemctl enable nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
[opc@thanksgiving-shopping-list ~]$ sudo systemctl start nginx
[opc@thanksgiving-shopping-list ~]$
All that’s left to do is to clone the TodoMVC repo and copy the particular example folder into the web server root directory:
[opc@thanksgiving-shopping-list ~]$ git clone https://github.com/tastejs/todomvc
Cloning into 'todomvc'...
remote: Enumerating objects: 33889, done.
remote: Total 33889 (delta 0), reused 0 (delta 0), pack-reused 33889
Receiving objects: 100% (33889/33889), 71.29 MiB | 20.18 MiB/s, done.
Resolving deltas: 100% (13144/13144), done.
[opc@thanksgiving-shopping-list ~]$ sudo cp -r todomvc/examples/react/* /usr/share/nginx/html/
Once you have done that, you should be able to bring up the ToDo example by hitting your machine’s IP address in the browser:
Amending the app to become a shopping list
As the next step, I had to amend the app to make it look more like a shopping list and less like a todo list. The example is self-contained and doesn’t include many files.
[opc@thanksgiving-shopping-list ~]# tree /usr/share/nginx/html/
.
├── 404.html
├── 50x.html
├── index.html
├── js
│ ├── app.jsx
│ ├── footer.jsx
│ ├── todoItem.jsx
│ ├── todoModel.js
│ └── utils.js
├── nginx-logo.png
├── node_modules
│ ├── classnames
│ │ └── index.js
│ ├── director
│ │ └── build
│ │ └── director.js
│ ├── react
│ │ └── dist
│ │ ├── JSXTransformer.js
│ │ └── react-with-addons.js
│ ├── todomvc-app-css
│ │ └── index.css
│ └── todomvc-common
│ ├── base.css
│ └── base.js
├── package.json
├── poweredby.png
└── readme.md
9 directories, 19 files
In order for it to become a shopping list, all that needed to change were a couple of lines in the index.html
and js/app.jsx
files:
In the index.html
, I changed the title in line 5 and the “Double-click to edit a todo” in line 12. Optionally, you may want to change or remove lines 13 and 14.
Original index.html
:
<!doctype html>
<html lang="en" data-framework="react">
<head>
<meta charset="utf-8">
<title>React • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
</head>
<body>
<section class="todoapp"></section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/react/dist/react-with-addons.js"></script>
<script src="node_modules/classnames/index.js"></script>
<script src="node_modules/react/dist/JSXTransformer.js"></script>
<script src="node_modules/director/build/director.js"></script>
<script src="js/utils.js"></script>
<script src="js/todoModel.js"></script>
<!-- jsx is an optional syntactic sugar that transforms methods in React's
`render` into an HTML-looking format. Since the two models above are
unrelated to React, we didn't need those transforms. -->
<script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.jsx"></script>
</body>
</html>
Modified index.html
:
<!doctype html>
<html lang="en" data-framework="react">
<head>
<meta charset="utf-8">
<title>Thanksgiving Shopping List</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
</head>
<body>
<section class="todoapp"></section>
<footer class="info">
<p>Double-click to edit a item</p>
<p>Created by <a href="http://github.com/gvenzl/">gvenzl</a></p>
</footer>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/react/dist/react-with-addons.js"></script>
<script src="node_modules/classnames/index.js"></script>
<script src="node_modules/react/dist/JSXTransformer.js"></script>
<script src="node_modules/director/build/director.js"></script>
<script src="js/utils.js"></script>
<script src="js/todoModel.js"></script>
<!-- jsx is an optional syntactic sugar that transforms methods in React's
`render` into an HTML-looking format. Since the two models above are
unrelated to React, we didn't need those transforms. -->
<script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.jsx"></script>
</body>
</html>
At this point a quick shout-out to petehunt for putting this example together! Thanks a lot!
Then I changed that big “todos” and “What needs to be done?” on the page itself. Both of these are located in the js/app.jsx
file at line 157 and 160:
Original js/app.jsx
:
<div>
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder="What needs to be done?"
value={this.state.newTodo}
onKeyDown={this.handleNewTodoKeyDown}
onChange={this.handleChange}
autoFocus={true}
/>
</header>
{main}
{footer}
</div>
Modified js/app.jsx
:
<div>
<header className="header">
<h1>Shopping list</h1>
<input
className="new-todo"
placeholder="What needs to be bought?"
value={this.state.newTodo}
onKeyDown={this.handleNewTodoKeyDown}
onChange={this.handleChange}
autoFocus={true}
/>
</header>
{main}
{footer}
</div>
Once I did that and hit refresh in my browser, the page appeared much more like a shopping list:
The app was now ready to accept shopping list items and to check them off:
While at that stage the app was up and running and appeared just fine as a shopping list, it unfortunately did persist the items on local browser storage, meaning that if I wanted to open the list on a different device, it would be empty again and none of my items would show up. What I needed was some remote storage and changes to the app to send and retrieve the items from there.
Making the app “persistent”
The example app uses local browser storage via the built-in localStorage
JavaScript object, which you can find in the store
function at line 28 in the js/utils.js
file:
store: function (namespace, data) {
if (data) {
return localStorage.setItem(namespace, JSON.stringify(data));
}
var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || [];
},
In order to make the app show the same data across different devices and in the long run, the data needed to be stored somewhere else in a more permanent manner. Luckily, in the day and age of cloud, cloud-based storage and databases are readily available so my idea was to swap out the calls to localStorage
with some asynchronous REST calls with as minimal effort as possible, that will store and retrieve the data from somewhere else. The good news is that Oracle’s Always Free Autonomous Database cloud service offerings do come with REST Data Services out of the box and, as the name suggests, they are always free. Hence they were a perfect candidate to take on storage for the JSON object that the app produces.
Want to know more about how to create an Autonomous Database? Check out my previous blog post and look out for the “Always Free” option.
When you look at the app you will see that it only stores and retrieves a single JSON object that contains an array of the items on the shopping list, completed as well as still outstanding ones. In the case that one adds to the list, removes an item from it, or changes an existing one, the app will regenerate that JSON object to reflect these changes.
[
{
"id": "646e079c-6eff-41fc-b9a5-dbe825e11635",
"title": "Turkey",
"completed": false
},
{
"id": "4fc91b3b-4d4a-41c5-8a70-6bdfe823decc",
"title": "Gravy",
"completed": false
},
{
"id": "20074b58-40a7-435c-8ac2-c802bc8ec595",
"title": "Beer",
"completed": true
}
]
As I wanted to keep this really simple and without major refactoring to the code (as said at the beginning, MVP), all I really needed was a single REST API that stores and retrieves that JSON object. Essentially that translates to a single-column table that would only ever have 1 row in it and then putting a GET
and POST
REST handler on top of it. Luckily, this can be easily done via just a few SQL commands with Autonomous Database (or Oracle Database + ORDS):
-- Create table "SHOPPINGLIST" to hold shopping list object
CREATE TABLE shoppinglist (items CLOB);
-- Create REST endpoints
BEGIN
-- Enable schema for REST calls
ORDS.ENABLE_SCHEMA(
p_enabled => TRUE,
p_schema => 'ADMIN',
p_url_mapping_type => 'BASE_PATH',
p_url_mapping_pattern => 'shoppinglist',
p_auto_rest_auth => FALSE);
-- Create new REST module "Shopping List persistence"
ORDS.DEFINE_MODULE(
p_module_name => 'Shopping List persistence',
p_base_path => '/storage/',
p_items_per_page => 25,
p_status => 'PUBLISHED',
p_comments => NULL);
-- Create new template
ORDS.DEFINE_TEMPLATE(
p_module_name => 'Shopping List persistence',
p_pattern => 'items',
p_priority => 0,
p_etag_type => 'HASH',
p_etag_query => NULL,
p_comments => NULL);
-- Define POST REST handler to store JSON object
ORDS.DEFINE_HANDLER(
p_module_name => 'Shopping List persistence',
p_pattern => 'items',
p_method => 'POST',
p_source_type => 'plsql/block',
p_items_per_page => 25,
p_mimes_allowed => '',
p_comments => NULL,
p_source =>
'DECLARE
v_rows PLS_INTEGER;
v_payload CLOB := :body_text;
BEGIN
SELECT COUNT(1) INTO v_rows FROM shoppinglist;
IF v_rows = 0 THEN
INSERT INTO shoppinglist (items) VALUES (v_payload);
ELSE
UPDATE shoppinglist SET items = v_payload;
END IF;
END;'
);
-- Define GET handler to retrieve JSON object
ORDS.DEFINE_HANDLER(
p_module_name => 'Shopping List persistence',
p_pattern => 'items',
p_method => 'GET',
p_source_type => 'json/collection',
p_items_per_page => 25,
p_mimes_allowed => '',
p_comments => NULL,
p_source =>
'SELECT items FROM shoppinglist'
);
COMMIT;
END;
The important lines in the SQL script above are lines:
- 2: which creates the single-column table called “shoppinglist”
- 11, 17 & 25 which define the URL that the endpoint will be available at
.../shoppinglist/storage/items
- 32, 35, 41 – 51 which define the
POST
handler that executes the DML for inserting or updating the shopping list items object - 55, 58 & 64 which define the
GET
handler that retrieves the object from the table
Next and as the last step, the app itself needed to be modified to issue a REST call instead of using localStorage
. The changes are mostly in js/utils.js
with small modifications in js/app.jsx
and js/todoModel.js
to allow for the change to an asynchronous behavior.
In js/utils.js
I introduced a new object remoteStorage
to deal with the remote storage and to keep the original code as little changed as possible. Note that it has a placeholder for the REST URL on line 8:
var app = app || {};
(function () {
'use strict';
app.remoteStorage = {
restURL: "<Your REST URL>",
setItem: async function (namespace, data) {
const request = await fetch(this.restURL, {
method: 'POST',
body: data
});
},
getItem: async function (namespace) {
const request = await fetch(this.restURL)
const result = await request.json();
if( request.ok ) {
const items = result.items;
if (result.items.length > 0) {
return JSON.parse(result.items[0].items);
}
}
}
};
Then I changed the original store
function to call the new remoteStorage
instead of localStorage
. Because the REST calls are done as an asynchronous call, the function itself also needed to be defined as async
:
store: async function (namespace, data) {
if (data) {
// localStorage.setItem(namespace, JSON.stringify(data));
await app.remoteStorage.setItem (namespace, JSON.stringify(data));
return;
}
// var store = localStorage.getItem(namespace);
var store = await app.remoteStorage.getItem(namespace);
return store || [];
},
In js/todoModel.js
at line 18, instead of just retrieving the list of items from Utils.store()
which used to issue a synchronous call to localStorage
, I instead initialized it to an empty array and then added a new asynchronous function to fetch the items from the remote storage:
app.TodoModel = function (key) {
this.key = key;
this.todos = [];
this.onChanges = [];
};
app.TodoModel.prototype.fetchTodos = async function() {
this.todos = await Utils.store(this.key);
};
All that was left was to call the new fetchTodos
function in js/app.jsx
file as part of the didComponentMount
function, which now also had to become asynchronous…
componentDidMount: async function () {
await this.props.model.fetchTodos();
var setState = this.setState;
… and tell the render
function to return an empty array in case there are no items yet:
render: function () {
var footer;
var main;
var todos = this.props.model.todos || [];
The result is an over the web persistent shopping list backed by a REST enabled database that can be shared with others:
Last but not least, a quick shout-out to Todd Sharp, who saved my sanity by helping me with the asynchronous function calls. 🙂
What’s next? What’s missing?
As mentioned at the beginning, the app I have put together was always intended as an MVP. Because of that, there are areas that are lacking or could be better, of course.
Now that the app is using a database as the backend storage, there is really no need to persist just one big JSON object with all the shopping list items within it. Instead, the app could be rewritten to treat each shopping list item as an individual record that represents a row within a three-column table within the database.
Alternatively, given that Oracle Database and Autonomous Database both have native JSON support, the app could also be rewritten to store the shopping list items in a JSON collection within the database, each item on the list representing a single JSON document.
The REST endpoint itself should only be available to properly authenticated applications and not, like right now, to the public.
And, of course, as the app is already using a database, it could also provide individual shopping lists to users that can log in and out from the app.
Are you up for a challenge?
If this little app inspired you and you would like to flex your coding muscles a bit, why don’t you have a go at the outstanding items I’ve just listed? Or perhaps build your own little persistent shopping list app? You can find the code at https://github.com/gvenzl/Oracle-shopping-list-app. Feel free to share the results with me either as a comment on this post or on Twitter (just tag me @GeraldVenzl and use the hashtag #shoppinglistapp).
Hey @GeraldVenzl, I have just built my own #shoppinglistapp. Check it out here: [URL]
Tweet
This post was originally published at https://blogs.oracle.com/developers/thanksgiving-shopping-list
One thought on “How to build your own Thanksgiving shopping list app with JavaScript”