Skip to content

Commit b8c99cf

Browse files
authored
Merge pull request #11892 from Automattic/netlify-functions-example
Netlify functions example
2 parents 92cb6fb + 2751883 commit b8c99cf

26 files changed

+728
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ test/files/main.js
4141

4242
package-lock.json
4343

44-
.config*
44+
.config.js
4545

4646
# Compiled docs
4747
docs/*.html
@@ -50,6 +50,9 @@ docs/typescript/*.html
5050
docs/api/*.html
5151
index.html
5252

53+
# Local Netlify folder
54+
.netlify
55+
5356
# yarn package-lock
5457
yarn.lock
5558

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = Object.freeze({
4+
mongodbUri: 'mongodb://localhost:27017/ecommerce',
5+
stripeSecretKey: 'YOUR STRIPE KEY HERE',
6+
success_url: 'localhost:3000/success',
7+
cancel_url: 'localhost:3000/cancel'
8+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV) {
4+
try {
5+
module.exports = require('./' + process.env.NODE_ENV);
6+
console.log('Using ' + process.env.NODE_ENV);
7+
} catch (err) {
8+
module.exports = require('./development');
9+
}
10+
} else {
11+
console.log('using production');
12+
module.exports = require('./production');
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
module.exports = Object.freeze({
4+
mongodbUri: 'mongodb://localhost:27017/ecommerce_test',
5+
stripeSecretKey: 'test',
6+
success_url: 'localhost:3000/success',
7+
cancel_url: 'localhost:3000/cancel'
8+
9+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"imports":{"netlify:edge":"https://edge-bootstrap.netlify.app/v1/index.ts"}}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# ecommerce-netlify-functions
2+
3+
This sample demonstrates using Mongoose to build an eCommerce shopping cart using [Netlify Functions](https://www.netlify.com/products/functions/), which runs on [AWS Lambda](https://mongoosejs.com/docs/lambda.html).
4+
5+
Other tools include:
6+
7+
1. Stripe for payment processing
8+
2. [Mocha](https://masteringjs.io/mocha) and [Sinon](https://masteringjs.io/sinon) for testing
9+
10+
## Running This Example
11+
12+
1. Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/development.js` to your MongoDB server's address.
13+
2. Run `npm install`
14+
3. Run `npm run seed`
15+
4. Run `npm start`
16+
5. Visit `http://localhost:8888/.netlify/functions/getProducts` to list all available products
17+
6. Run other endpoints using curl or postman
18+
19+
## Testing
20+
21+
Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/test.js` to your MongoDB server's address.
22+
Then run `npm test`.
23+
24+
```
25+
$ npm test
26+
27+
> test
28+
> env NODE_ENV=test mocha ./test/*.test.js
29+
30+
Using test
31+
32+
33+
Add to Cart
34+
✔ Should create a cart and add a product to the cart
35+
✔ Should find the cart and add to the cart
36+
✔ Should find the cart and increase the quantity of the item(s) in the cart
37+
38+
Checkout
39+
✔ Should do a successful checkout run
40+
41+
Get the cart given an id
42+
✔ Should create a cart and then find the cart.
43+
44+
Products
45+
✔ Should get all products.
46+
47+
Remove From Cart
48+
✔ Should create a cart and then it should remove the entire item from it.
49+
✔ Should create a cart and then it should reduce the quantity of an item from it.
50+
51+
52+
8 passing (112ms)
53+
54+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const config = require('./.config');
4+
const mongoose = require('mongoose');
5+
6+
let conn = null;
7+
8+
module.exports = async function connect() {
9+
if (conn != null) {
10+
return conn;
11+
}
12+
conn = mongoose.connection;
13+
await mongoose.connect(config.mongodbUri);
14+
return conn;
15+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
const config = require('../.config')
4+
5+
module.exports = require('stripe')(config.stripeSecretKey);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
const mongoose = require('mongoose');
3+
4+
const productSchema = new mongoose.Schema({
5+
name: String,
6+
price: Number,
7+
image: String
8+
});
9+
10+
const Product = mongoose.model('Product', productSchema);
11+
12+
module.exports.Product = Product;
13+
14+
const orderSchema = new mongoose.Schema({
15+
items: [
16+
{ productId: { type: mongoose.ObjectId, required: true, ref: 'Product' },
17+
quantity: { type: Number, required: true, validate: v => v > 0 }
18+
}
19+
],
20+
total: {
21+
type: Number,
22+
default: 0
23+
},
24+
status: {
25+
type: String,
26+
enum: ['PAID', 'IN_PROGRESS', 'SHIPPED', 'DELIVERED'],
27+
default: 'PAID'
28+
},
29+
orderNumber: {
30+
type: Number,
31+
required: true
32+
},
33+
name: {
34+
type: String,
35+
required: true
36+
},
37+
email: {
38+
type: String,
39+
required: true
40+
},
41+
address1: {
42+
type: String,
43+
required: true
44+
},
45+
address2: {
46+
type: String
47+
},
48+
city: {
49+
type: String,
50+
required: true
51+
},
52+
state: {
53+
type: String,
54+
required: true
55+
},
56+
zip: {
57+
type: String,
58+
required: true
59+
},
60+
shipping: {
61+
type: String,
62+
required: true,
63+
enum: ['standard', '2day']
64+
},
65+
paymentMethod: {
66+
id: String,
67+
brand: String,
68+
last4: String
69+
}
70+
}, { optimisticConcurrency: true });
71+
72+
const Order = mongoose.model('Order', orderSchema);
73+
74+
module.exports.Order = Order;
75+
76+
const cartSchema = new mongoose.Schema({
77+
items: [{ productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, quantity: { type: Number, required: true } }],
78+
orderId: { type: mongoose.ObjectId, ref: 'Order' }
79+
}, { timestamps: true });
80+
81+
const Cart = mongoose.model('Cart', cartSchema);
82+
83+
module.exports.Cart = Cart;
84+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
const { Cart, Product } = require('../../models');
4+
const connect = require('../../connect');
5+
6+
const handler = async(event) => {
7+
try {
8+
event.body = JSON.parse(event.body || {});
9+
await connect();
10+
const products = await Product.find();
11+
if (event.body.cartId) {
12+
// get the document containing the specified cartId
13+
const cart = await Cart.findOne({ _id: event.body.cartId }).setOptions({ sanitizeFilter: true });
14+
15+
if (cart == null) {
16+
return { statusCode: 404, body: JSON.stringify({ message: 'Cart not found' }) };
17+
}
18+
if(!Array.isArray(event.body.items)) {
19+
return { statusCode: 500, body: JSON.stringify({ error: 'items is not an array' }) };
20+
}
21+
for (const product of event.body.items) {
22+
const exists = cart.items.find(item => item?.productId?.toString() === product?.productId?.toString());
23+
if (!exists && products.find(p => product?.productId?.toString() === p?._id?.toString())) {
24+
cart.items.push(product);
25+
await cart.save();
26+
} else {
27+
exists.quantity += product.quantity;
28+
await cart.save();
29+
}
30+
}
31+
32+
if (!cart.items.length) {
33+
return { statusCode: 200, body: JSON.stringify({ cart: null }) };
34+
}
35+
36+
await cart.save();
37+
return { statusCode: 200, body: JSON.stringify(cart) };
38+
} else {
39+
// If no cartId, create a new cart
40+
const cart = await Cart.create({ items: event.body.items });
41+
return { statusCode: 200, body: JSON.stringify(cart) };
42+
}
43+
} catch (error) {
44+
return { statusCode: 500, body: error.toString() };
45+
}
46+
};
47+
48+
module.exports = { handler };

0 commit comments

Comments
 (0)