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

#
  • Different in name, module, routeName and routePath
  • All call() functions are changed to api() for accessing backend API
  • The 'crud-api/contact/add' refer to the action in crud-api/contact component
  • The extractFields(%_fields%) convert _.fields to key-value pairs

  • 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()
             }

    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 })
    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 })
    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)
    ])
    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()
       })
    ])