Woodshop
Search…
Use Azure Table Storage
This How to is based on the Medium article by Andrew Varnon
The implementation is done using a Smart Collection and a CRUD service that will wrap the Azure Table Storage API.

The Table Storage Definition

You can use the new Azure Data Explorer to create and populate a Table Storage in your Azure Storage account.
In our example, we are going to use the Table Customers with the fields:
    Id: PartitionKey + RowKey
    Timestamp (updated at)
    Email as String
    FirstName as String
    LastName as String

Install Azure data-tables package

1
npm install @azure/data-tables --save
Copied!

Smart Collection definition

1
const { collection } = require('forest-express-sequelize');
2
3
collection('customers', {
4
fields: [{
5
field: 'id',
6
type: 'String',
7
get: (customer) => `${customer.partitionKey}|${customer.rowKey}`,
8
}, {
9
field: 'partitionKey',
10
type: 'String',
11
}, {
12
field: 'rowKey',
13
type: 'String',
14
}, {
15
field: 'timestamp',
16
type: 'Date',
17
}, {
18
field: 'Email',
19
type: 'String',
20
}, {
21
field: 'LastName',
22
type: 'String',
23
}, {
24
field: 'FirstName',
25
type: 'String',
26
}, ],
27
});
Copied!

The Azure Data Tables Service Wrapper

1
const { TableClient } = require('@azure/data-tables');
2
3
const getClient = (tableName) => {
4
const client = TableClient.fromConnectionString(
5
process.env.AZURE_STORAGE_CONNECTION_STRING,
6
tableName);
7
return client;
8
}
9
10
const azureTableStorageService = {
11
deleteEntityAsync: async (tableName, partitionKey, rowKey) => {
12
const client = getClient(tableName);
13
await client.deleteEntity(partitionKey, rowKey);
14
},
15
16
getEntityAsync: async (tableName, partitionKey, rowKey) => {
17
const client = getClient(tableName);
18
return client.getEntity(partitionKey, rowKey);
19
},
20
21
listEntitiesAsync: async (tableName, options) => {
22
const client = getClient(tableName);
23
var azureResponse = await client.listEntities();
24
25
let iterator = await azureResponse.byPage({maxPageSize: options.pageSize});
26
27
for (let i = 1; i < options.pageNumber; i++) iterator.next(); // Skip pages
28
29
let entities = await iterator.next();
30
let records = entities.value.filter(entity => entity.etag);
31
32
// Load an extra page if we need to allow (Next Page)
33
const entitiesNextPage = await iterator.next();
34
let nbNextPage = 0;
35
if (entitiesNextPage && entitiesNextPage.value) {
36
nbNextPage = entitiesNextPage.value.filter(entity => entity.etag).length;
37
}
38
39
// Azure Data Tables does not provide a row count.
40
// We just inform the user there is a new page with at least x items
41
const minimumRowEstimated = (options.pageNumber-1) * options.pageSize + records.length + nbNextPage;
42
43
return {records, count: minimumRowEstimated};
44
},
45
46
createEntityAsync: async (tableName, entity) => {
47
const client = getClient(tableName);
48
delete entity['__meta__'];
49
await client.createEntity(entity);
50
return client.getEntity(entity.partitionKey, entity.rowKey);
51
},
52
53
udpateEntityAsync: async (tableName, entity) => {
54
const client = getClient(tableName);
55
await client.updateEntity(entity, "Replace");
56
return client.getEntity(entity.partitionKey, entity.rowKey);
57
},
58
59
};
60
61
module.exports = azureTableStorageService;
Copied!

Routes definition

1
const express = require('express');
2
const { PermissionMiddlewareCreator, RecordCreator, RecordUpdater } = require('forest-express');
3
const { RecordSerializer } = require('forest-express');
4
5
const router = express.Router();
6
7
const COLLECTION_NAME = 'customers';
8
const permissionMiddlewareCreator = new PermissionMiddlewareCreator(COLLECTION_NAME);
9
const recordSerializer = new RecordSerializer({ name: COLLECTION_NAME });
10
11
const azureTableStorageService = require("../services/azure-table-storage-service");
12
13
// Get a list of Customers
14
router.get(`/${COLLECTION_NAME}`, permissionMiddlewareCreator.list(), async (request, response, next) => {
15
const pageSize = parseInt(request.query.page.size) || 15;
16
const pageNumber = parseInt(request.query.page.number);
17
18
azureTableStorageService.listEntitiesAsync(COLLECTION_NAME, {pageSize, pageNumber})
19
.then( async ({records, count}) => {
20
const recordsSerialized = await recordSerializer.serialize(records);
21
response.send({ ...recordsSerialized, meta: { count }});
22
})
23
.catch ( (e) => {
24
console.error(e);
25
next(e);
26
27
});
28
});
29
30
// Get a Customer
31
router.get(`/${COLLECTION_NAME}/:recordId`, permissionMiddlewareCreator.details(), async (request, response, next) => {
32
const parts = request.params.recordId.split('|');
33
azureTableStorageService.getEntityAsync(COLLECTION_NAME, parts[0], parts[1])
34
.then( (record) => recordSerializer.serialize(record))
35
.then( (recordSerialized) => response.send(recordSerialized))
36
.catch ( (e) => {
37
console.error(e);
38
next(e);
39
});
40
});
41
42
// Create a Customer
43
router.post(`/${COLLECTION_NAME}`, permissionMiddlewareCreator.create(), async (request, response, next) => {
44
const recordCreator = new RecordCreator({name: COLLECTION_NAME}, request.user, request.query);
45
recordCreator.deserialize(request.body)
46
.then( (recordToCreate) => {
47
return azureTableStorageService.createEntityAsync(COLLECTION_NAME, recordToCreate);
48
})
49
.then((record) => recordSerializer.serialize(record))
50
.then((recordSerialized) => response.send(recordSerialized))
51
.catch ( (e) => {
52
console.error(e);
53
next(e);
54
});
55
});
56
57
// Update a Customer
58
router.put(`/${COLLECTION_NAME}/:recordId`, permissionMiddlewareCreator.update(), async (request, response, next) => {
59
const parts = request.params.recordId.split('|');
60
const recordUpdater = new RecordUpdater({name: COLLECTION_NAME}, request.user, request.query);
61
recordUpdater.deserialize(request.body)
62
.then( (recordToUpdate) => {
63
recordToUpdate.partitionKey = parts[0];
64
recordToUpdate.rowKey = parts[1];
65
return azureTableStorageService.udpateEntityAsync(COLLECTION_NAME, recordToUpdate);
66
})
67
.then( (record) => recordSerializer.serialize(record) )
68
.then( (recordSerialized) => response.send(recordSerialized) )
69
.catch ( (e) => {
70
console.error(e);
71
next(e);
72
});
73
74
});
75
76
// Delete a list of Customers
77
router.delete(`/${COLLECTION_NAME}`, permissionMiddlewareCreator.delete(), async (request, response, next) => {
78
try {
79
for (const key of request.body.data.attributes.ids) {
80
const parts = key.split('|');
81
await azureTableStorageService.deleteEntityAsync(COLLECTION_NAME, parts[0], parts[1]);
82
}
83
response.status(204).send()
84
} catch (e) {
85
console.error(e);
86
next(e);
87
}
88
});
89
90
module.exports = router;
Copied!

Result

Last modified 2mo ago