Zza! Zza! (Node/MongoDB)

Zza! is a single page application (SPA) for ordering pizzas, salads and drinks.

It's 100% JavaScript, written for Node.js running Express, and uses a MongoDB database.

Zza! uses the BMEAN stack:

Breeze client-side data modeling and data access
Mongo server-side document-oriented database
Express server-side web application framework on node
Angular client-side presentation framework
Node server-side platform for web applications

Breeze works well with Microsoft technologies as we've demonstrated many times. It also works well outside the Microsoft ecosystem. To drive that point home, this sample has ...

no C#
no ASP.NET
no Web API
no IIS
no Entity Framework
no SQL Server

Download

Download ALL of the Breeze JavaScript samples from github as a zip file.

In this case we're interested in the "Zza" sample, located in the node/zza-node-mongo directory. These instructions assume this will be your current working directory.

At the top level you will find:

  • a readme.md explaining how to install and run the app
  • the Zza! Mongo database folder
  • the client folder full of client application HTML, CSS, and JavaScript
  • the server folder containing the node/express server application JavaScript.

You can view, edit, and run the code in this project using the tools of your choice.

The sample assumes that you've installed node.js and MongoDb >= v.2.6

Run it!

The readme.md has the details. Just remember the three basic steps:

  1. Start your MongoDB server: mongod
  2. Start the Node/Express server: node server
  3. Launch a browser and navigate to localhost:3000

The app should load on the home screen with a big picture of "Zza" himself. Click the "Order" link in the menu bar and you'll see what's on the menu beginning with pizzas.

The "Salad" and "Drinks" links switch the view to similar views of those products.

You want the "Holy Smokes" pizza so you click on it. You're taken to a screen where you can pick the size and add toppings. Go for the "Large", pick the "Wheat Crust", and pile on some "BBQ Chicken" from the "Meat" toppings. You're hungry so why not order two of these babies?

The dashboard on the left displays that we're considering a "Holy Smokes" pizza but haven't put it in our cart yet. Click the "Add to Cart" button and it moves up the dashboard into the "Order Summary" panel.

Click the "View Cart" link (or the "Cart" button on the upper-right) and we'll see our order so far:

Go back to "Order" and load up on Salads and Drinks.

Inside the app

A single web page, index.html, frames the story. Most of it is css and JavaScript file loading. In the middle there's a tiny bit of layout:

<body class="ng-cloak" data-ng-app="app">

    <!-- *** Shell of the SPA  *** -->
    <div data-ui-view="header"></div>

    <div id="shell-content" data-ui-view="content"></div>

    <div data-ui-view="footer"></div>

    <!-- *** Vendor scripts  *** -->
    <!-- App javascript libraries -->
</body>

Angular.js

Angular.js is running the show. It asynchronously loads header and footer HTML.

In the middle are three <div> tags each with a data-ui-view attribute. Angular dynamically loads templated views into these tags based on a "route". The middle <div> called "shell-content" displays most of what you see. It could show the "Home" page with the picture of "Zza"; it could show the "About" page; it could display a product menu page, an order page, or the cart page. It all depends upon the route ... which you see in the address bar:

Pizza menu

In this example, the route identifies the pizza product whose id is "3". Angular uses route information (in public/app/services/routes.js) to download the correct view (a file in the public/app/views directory) and marry it to a controller (a JavaScript file in the public/app/controllers directory).

All the displayed values are data bound either to a controller (e.g., orderItem defined in app/order/orderItem.js) or to a Breeze entity exposed by the controller (e.g., Product)

BreezeJS on the client

Breeze handles the application's data modeling and data access chores. "Entities" are JavaScript types constructed by a combination of developer code (in public/app/services/model.js) and Breeze metadata (in public/app/metadata.js).

The Breeze EntityManager and related components handle the details of creating, querying, materializing, caching, change-tracking, validating, and saving entity data.

While the controllers could interact directly with the Breeze EntityManager we prefer that controllers make data requests through a facade, here called the dataservice (client/app/services/dataservice). The dataservice provides the controllers with a higher level data abstraction, shielding them from application-specific details of working with Breeze.

The Zza dataservice API offers kind of looks like this:

{
    cartOrder: ...,
    draftOrder: ...,
    getCustomers: ...,
    getOrderHeadersForCustomer: ...,
    lookups: ...,
    ready: ...,
    saveChanges: ...
}

That's what we mean by "higher level data abstraction". If it reminds you of the Unit-of-Work and Repository patterns ... then you've caught on to what we have in mind.

The Zza! client is server agnostic

The client-side code is virtually unaware of the server technology. Everything you've learned about programming in Angular and Breeze applies without alteration.

To prove that point, the exact same Zza! client - HTML, JavaScript and CSS - runs in front of a .NET server, hosted on IIS, running the Web API, Entity Framework, and SQL Server.

Somethings have changed over time but this part remains essentially true.

Exactly four files are different:

  1. metadata.js, reflecting the difference between a relational schema and MongoDB document schema.

  2. model.js, for much the same reason

  3. environment.js, a small configuration file

  4. /scripts/breeze.dataservice.mongo.js, a Breeze "dataservice adapter" plug-in that handles low level details of communicating with node and a MongoDB server.

Otherwise, the same client-code runs in radically different environments.

Node and Express

The web application server is written in about 60 lines of JavaScript (server.js), using the Express framework running on the node.js platform.

This server is either delivering static content to the client such as HTML, CSS, image, and JavaScript files or routing client data requests to a data access module called routes.js.

Breeze on the Server

The server-side data management component (breeze-routes.js) is developer-written, application-specific code for handling Breeze client data requests such as queries and saves for Zza data .

Here you decide what data request your server will honor and implement the necessary business logic including validations.

This particular Zza! application server handles a small number of requests.

Most are unalloyed Breeze queries aimed a resource whose name is the plural of an Zza entity type. These are Breeze-enabled query endpoints that understand the OData query semantics familiar to Breeze client-side developers. For example, a Breeze client query such as:

breeze.EntityQuery.from('products')
.where ('name', 'gt', 'Caesar') // products whose name comes after `Caesar`
.orderBy('name')                    // sort by product name
.skip(5).take(5)                     // skip the first and take the second (pagesize = 5) 
.using(manager).execute();

becomes a GET request like this one:

http://localhost:3000/breeze/zza/products?$filter=name gt 'Caesar'&$orderby=name&$skip=5&$top=5

which is routed to the getQuery method.

The breeze-routes module is a controller that mediates between client requests and a repository that does the data access work. So the getQuery method delegates to repository.getsomething ("getproducts" in this case). "getproducts" passes the query to a breeze-mongo component that queries the Zza database and returns JSON product data. This data bubbles back up to the breeze-router which sends to the client something like the following:

[
    {
        _id: 31,
        type: "drink",
        name: "Cola",
        description: "Cola",
        image: "cola.jpg",
        hasOptions: false,
        isPremium: false,
        isVegetarian: false,
        withTomatoSauce: false,
        sizeIds: [
            10,
            11,
            12
        ]
    },
    {...},
    {...},
    {...},
    {...}
]

Plain old GET endpoints

This OData query facility is made possible thanks to the breeze-mongodb module that you installed with npm.

Your server doesn't have to support OData queries. You can simply return an object and let the Breeze client sort it out.

The lookups endpoint invokes getLookups, a method that returns a single JSON object whose properties are arrays of the four Zza reference entities:OrderStatus, Product, ProductOption, ProductSize.

With this one call, the client receives all of the reference items it needs to populate comboboxes and drive pricing calculations. The Breeze client automatically shreds the package and stows these entity lists in cache.

It bears repeating that the "magic" in this call is all on the client. The lookups implementation is standard JavaScript with four standard MongoDB queries in the inner getAll function.

Learn more here about the breeze-mongodb module and programming with breeze, node, and MongoDB.

The Zza! MongoDB database

The Zza! MongoDB database is pre-loaded with ample sample data.

100 Customers
513 Orders
1447 OrderItems on those orders
1773 OrderItemOptions (toppings) on those OrderItems

41 Products in the catalog (pizza, salads, drinks)
64 ProductOptions such as pizza toppings
15 ProductSize objects that define sizes ("Large 14") and prices for products of each size
6 OrderStatuses

The Customer and Order... stuff are the transactional data representing the activity of the Zza! pizza parlor.

In a relational database, the Order, OrderItems and OrderItemOptions would be in separate tables, related via foreign keys.

This is a Mongo database with "collections" instead of "tables" and "documents" instead of "rows". A document can have nested sub-documents.

In this database, there is an Orders collection but no OrderItems or OrderItemOptions collections. Instead, each Order has an "items" array of OrderItem sub-documents. Each OrderItem sub-document has an "options" array of OrderItemOption sub-documents.

Here's an illustration with a sample data snapshot in the background:

<img src="/sites/default/files/images/ZzaOrderSchema.jpg" style="width:100%; max-width:800px;">

Breeze metadata can describe complex documents like the Zza! Order document. Root documents - the objects like Order in MongoDB collections - map to Breeze EntityTypes. The sub-documents, which have no existence independent of their parent documents, map to Breeze ComplexTypes.

Breeze already supports scalar (single valued) complex types for relational databases. Relational databases don't have collection properties. Document databases do. So Breeze supports arrays of complex types.

Schema summary

The OrderStatuses, Products, ProductOptions, and ProductSizes are relatively static reference data. The application can't change them. Each has a document in its own MongoDB collection.

So the entire Zza! database consists of seven collections:

Customer
Order
OrderStatus
Product
ProductOption
ProductSize

On the backlog

This sample is a work in progress. Among the features yet to be implemented:

  • Place the order (and save it to the database)
  • Client- and server-side validation
  • Deliver a delicious, hot pizza to your laptop

Breeze is ready to tackle the first three; we just need a little more time to work on the code. We haven't figured out the pizza delivery system yet. We're thinking about it.