CRUD - Backend API
This tutorial is the extension of previous tutorial to integrate backend API using @uteamjs/node framework. The user interface and operations are exactly the same whereas the data is stored in a backend database through RESTful API.
Frontend Component
#app.yaml
#header:
name: Crud Api
...
module: crud-api # Module name
routeName: routeCrudApi
routePath: crud-api
...
contact:
...
- Buttons:
- Create:
href: '#/crud-api/detail'
- Delete:
...
click: >
() => api('delete', getSelectedRowID())
- Grid:
...
col:
...
- label: Name
route: crud-api/detail
detail:
...
- Buttons:
...
- Save:
click: >
() => {
api('crud-api/contact/add', extractFields(%_fields%))
goBack()
}
name: Crud Api
...
module: crud-api # Module name
routeName: routeCrudApi
routePath: crud-api
...
contact:
...
- Buttons:
- Create:
href: '#/crud-api/detail'
- Delete:
...
click: >
() => api('delete', getSelectedRowID())
- Grid:
...
col:
...
- label: Name
route: crud-api/detail
detail:
...
- Buttons:
...
- Save:
click: >
() => {
api('crud-api/contact/add', extractFields(%_fields%))
goBack()
}
contact.js
#import { utCreateElement, utReducer, merge } from '@uteamjs/react'
import { _layout, _reducer } from './contact_export'
const reducer = utReducer('crud-api/contact',
merge(_reducer, {
actions: {
delete: (_, rows) => _.rows = _.rows.filter(t =>
rows.toString().indexOf(t.id) < 0),
load: (_, payload) => _.rows = payload.rows
}
})
)
class layout extends _layout {
constructor(props) {
super(props)
props.api('load')
}
render = this.Content
}
export default utCreateElement({ reducer, layout })
import { _layout, _reducer } from './contact_export'
const reducer = utReducer('crud-api/contact',
merge(_reducer, {
actions: {
delete: (_, rows) => _.rows = _.rows.filter(t =>
rows.toString().indexOf(t.id) < 0),
load: (_, payload) => _.rows = payload.rows
}
})
)
class layout extends _layout {
constructor(props) {
super(props)
props.api('load')
}
render = this.Content
}
export default utCreateElement({ reducer, layout })
With the following modifications:
constructor()
The api('load') function triggers the backend load API.
action.load
payload is returned from the backend API with rows of data. Assign the rows to _.rows
action.delete
The Delete button triggers the api('delete', getSelectedRowID()) function which fetches selected rows to the backend API for deletion in the database then updates the _.rows to exclude deleted records.
detail.js
#import { utCreateElement, utReducer, merge, store } from '@uteamjs/react'
import { _layout, _reducer } from './detail_export'
import { each } from 'lodash'
const reducer = utReducer('crud-api/detail',
merge(_reducer, {
actions: {
load: (_, payload) =>
each(_.fields, (v, k) => v.value = payload.data[k])
}
})
)
class layout extends _layout {
constructor(props) {
super(props)
const { _, match, api } = props
const { id } = match.params
if (id)
api('load', { id })
else
each(_.fields, t => t.value = '')
}
render = this.Content
}
export default utCreateElement({ reducer, layout })
import { _layout, _reducer } from './detail_export'
import { each } from 'lodash'
const reducer = utReducer('crud-api/detail',
merge(_reducer, {
actions: {
load: (_, payload) =>
each(_.fields, (v, k) => v.value = payload.data[k])
}
})
)
class layout extends _layout {
constructor(props) {
super(props)
const { _, match, api } = props
const { id } = match.params
if (id)
api('load', { id })
else
each(_.fields, t => t.value = '')
}
render = this.Content
}
export default utCreateElement({ reducer, layout })
With the following modifications:
constructor()
The api('load', { id }) function triggers the backend load API with id as parameter.
Initialize the each <id>.value in _.field to empty string if no _.props.match.params.id passed in from React-Router
action.load
payload is returned from the backend API with a single key-value object. Assign the value back to _.field.<id>.value.
Backend API
#contact.js
#There is an exports function that matches each frontend api() call. The file is placed under the path .../packages/crud-api/contact.js such that the API is being routed automatically.
const { sqlseries, capitalize } = require('@uteamjs/node')
exports.load = sqlseries((db, data) => [
db.info('loading contacts'),
db.query('select * from contact', rows => {
rows.forEach(t => t.gender = capitalize(t.gender))
data.rows = rows
})
])
exports.add = sqlseries((db, payload) => [
db.updateInsert(null, payload.id, 'contact3', payload)
])
exports.delete = sqlseries((db, payload) => [
callback => eachSeries(payload, (item, cb) =>
db.queryParam('delete from contact where id = ?', item)(cb)
, callback)
])
exports.load = sqlseries((db, data) => [
db.info('loading contacts'),
db.query('select * from contact', rows => {
rows.forEach(t => t.gender = capitalize(t.gender))
data.rows = rows
})
])
exports.add = sqlseries((db, payload) => [
db.updateInsert(null, payload.id, 'contact3', payload)
])
exports.delete = sqlseries((db, payload) => [
callback => eachSeries(payload, (item, cb) =>
db.queryParam('delete from contact where id = ?', item)(cb)
, callback)
])
sqlseries()
Functions to serialize asynchronous statements.
db
Database object with functions for query, insert and update.
db.info()
Send information messages to frontend.
db.query()
General database query function by passing SQL statements.
db.queryParam()
General database query function by passing SQL statements with parameters.
db.updateInsert()
Database insert and update functions by passing table name and key-value data object.
capitalize()
Helper function to capitalize the first character of a sentence.
Please refer to RESTful API for details.
detail.js
#The coding is similar to contact.js except a single record rows[0] is assigned to payload.data.
const ut = require('@uteamjs/node')
exports.load = ut.sqlseries((db, payload) => [
db.query(`select * from contact where id='#123;payload.id}'`, rows => {
payload.data = rows[0]
payload.data.gender = payload.data.gender.toLowerCase()
})
])
exports.load = ut.sqlseries((db, payload) => [
db.query(`select * from contact where id='#123;payload.id}'`, rows => {
payload.data = rows[0]
payload.data.gender = payload.data.gender.toLowerCase()
})
])