Using a Database
In this guide we will implement the location service by creating a database to store our locations.
Generating a Database Access Class
First, let's generate the class that will access the database:
$ bb generate database -d location
If you open up the generated database class in LocationDatabase
you will see the following:
import {database} from 'blackbox-database'
import Location from './Location';
import {named} from 'blackbox-ioc';
@database()
@named('location-database')
export default class LocationDatabase {
async getLocation(_name:string): Promise<Location> {return <Location>{}}
async getLocationList(): Promise<Location[]> {return <Location[]>[]}
async createLocation(_location: Location) {}
async updateLocation(_name: string, _location: Location) {}
async deleteLocation(_name: string) {}
}
You may notice that the methods of the class have no meaningful body.
This is because the @database()
annotation will
replace the class prototype's methods with generated database access logic.
Typically the database class name will be of the form TypeDatabase
.
If your class name does not match this pattern, or if your datatype is identified
by something other than a name
parameter, then you can pass parameters to the
@database
annotation to specify these.
For example, a user may be identified by its username as follows:
@database('User', 'username')
The @named
annotation creates an instance of the class that can be used
elsewhere in your code, such as in your service implementation.
The class' method names follow a specific pattern that tells the @database
annotation
how to generate the method body. Method names should be written according to the following regular expression:
^(get|create|update|delete)${typeName}(ListBy|ListWithQuery|List|By)?([A-Z][A-Za-z0-9_]*)?$
where ${typeName}
is the name of the type either specified in the @database
annotation parameters if provided or in the class name preceding the word 'Database' otherwise.
You can create getters that will select only specific records by choosing the method name carefully.
For example, if you want to get a user by their given name (assuming your user datatype has a givenName field),
you could name a method getUserByGivenName
and the method will be generated accordingly.
Setting Up the Database
Currently the blackbox-database
package only supports MongoDB.
To setup the MongoDB database you must use the configureDatabase()
function which
can be imported from the blackbox-database
package. It takes a single configuration
parameter with url
field for specifying the MongoDB URL, and a dbname
field for specifying the name of the database.
For guidance on setting up a MongoDB database see the MongoDB
Getting Started documentation.
We will not cover the setup of the database in this guide, but you will need to setup a database named bb
and run it on localhost on port 27017.
In our case we will setup the database in the index.ts
file.
Open index.ts
and add the following import:
import {configureDatabase} from 'blackbox-database'
Then, find the call to initRootServices()
and add the following on a new line after it:
configureDatabase({
url: 'mongodb://localhost:27017',
dbname: 'bb'
})
We now have a database setup and a class for accessing it. Next, we will implement the Location service to use the database.
Connect the Service to the Database
The LocationServiceImpl
class includes a service method named getLocationList
.
This will be called when we hit the /location
endpoint with a GET
request.
It should return an array of Location objects.
First, add an autowired reference to the LocationDatabase class:
@autowired('location-database')
db: LocationDatabase
Then load the location array from the database:
getLocationList(props:any): Promise<Location[]> {
return this.db.getLocationList()
}
Notice that we had to change the return type of the method since the database access class operates asynchronousely.
Now, make sure your database is up and running, then rebuild and restart the server:
$ npm run build && npm run start
If you make a get request to /location
you should now receive an empty array read from your database.
[]
Create and Read from the Database
The final thing we will do in this guide is implement the other Location services.
Add a body to the createLocation()
method as follows:
createLocation({location}:any): Promise<void|NamedReference> {
return this.db.createLocation(location)
.then(() => {name: location.name})
}
The parameter passed to the method include req
(a request object),
verbose
which is true if the verbose
query parameter was included in the URL,
and the object to be created, which in this case is named location
.
We are only interested in the Location object, so we extract that from the method parameter.
The returned object must include a name
identifying the created entity.
Rebuild and restart. Then, using a tool such as Postman, post a location object to the
/location
end-point, for example:
{
"name": "Hobart",
"coordinates": {
"longitude": 42.8821,
"latitude": 147.3272
}
}
Make a GET
request to /location
and should now see our newly created location
in the returned array.
The other methods all correspond to the /location/{name}
endpoint and can be implemented as follows:
getLocation({name}:any): Promise<Location> {
return this.db.getLocation(name)
}
replaceLocation({location}:any): Promise<void|NamedReference> {
return this.db.updateLocation(location.name, location)
.then(() => {name: location.name})
}
updateLocation({location}:any): Promise<void|NamedReference> {
return this.db.getLocation(location.name)
.then((oldLocation) => this.db.updateLocation(location.name, Object.assign(oldLocation, location)))
.then(() => {name: location.name})
}
deleteLocation([name]:any): Promise<void|NamedReference> {
return this.db.deleteLocation(name)
.then(() => {name})
}
These methods respectively correspond to HTTP
methods as follows:
GET
, PUT
, PATCH
and DELETE
.
Gives these a try, for example you could use the location we created previously
at /location/Hobart
.