InterSystems IRIS REST Application Patterns
Tags: rest, tutorial, programming
Published: 2022-02-23 14:54:35

This article suggests to you some patterns to create REST API applications using IRIS.

Note: source code in https://github.com/yurimarx/movie

## Class Pattern to the REST Application
To begin, see my suggestion for classes needed to create IRIS API applications:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdplqjtvyaaio8bchnzq.png)

- IRISRESTApplication: CSP.REST class that will be the central controller for all REST requests and responses processed by the business services.
- BusinessService: class with a business topic implementation. It can use one or more Persistent Domain Classes to persist and query data required by the business topic requirements.
- Persistent Domain: persistent class to manage a SQL table. 

## Prereqs
- VSCode;
- Docker Desktop;
- InterSystems ObjectScript Extension Pack.

## Class Diagram to the Sample Application
- I will create a Movie Catalog application to demonstrate the patterns suggested in the article:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qq7qouy3bt5vnjjaosax.png)

Note: thanks to the https://openexchange.intersystems.com/package/iris-rest-api-template application. It was the base to this tutorial.

## Setup the Sample Application 

1. Create a folder movie in your file system. Open this folder in a new VSCode window
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdi70drh4bpbekw1ezq1.png)

2. Create the Dockerfile file inside movie folder to run IRIS Community edition into a Docker container instance. Content:

```
ARG IMAGE=intersystemsdc/iris-community:2020.3.0.221.0-zpm
ARG IMAGE=intersystemsdc/iris-community:2020.4.0.524.0-zpm
ARG IMAGE=intersystemsdc/iris-community

FROM $IMAGE
 
USER root   

WORKDIR /opt/irisapp

RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER ${ISC_PACKAGE_MGRUSER}
 
COPY  src src

COPY module.xml module.xml

COPY iris.script /tmp/iris.script
 
RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script \
    && iris stop IRIS quietly
```
 
3. Create the docker-compose.yml file inside movie folder to allows you run your docker instance and other instances together (not in this sample, but it is a good practice run from docker-compose instead dockerfile. Content:

```
version: '3.6'
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 1972:1972
      - 52773:52773
      - 53773
    volumes:
      - ./:/irisdev/app
```

4. Create the iris.script file inside movie folder to do some actions before run IRIS. This file is important to do custom terminal actions necessary for the application, like disable password expiration. Content:

```
do $System.OBJ.LoadDir("/opt/irisapp/src","ck",,1)

zn "%SYS"

Do ##class(Security.Users).UnExpireUserPasswords("*")

zn "USER"

zpm "load /opt/irisapp/ -v":1:1

halt
```

5. Create the module.xml file inside movie folder to install and run your application using ZPM. This file is important to do the application endpoint configuration and install swagger-ui (web app used to run and test your API using swagger file). Content:

```
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="movie.ZPM">
    <Module>
      <Name>movie</Name>
      <Version>1.0.0</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="dc.movie.PKG"/>
      <Dependencies>
        <ModuleReference>
          <Name>swagger-ui</Name>
          <Version>1.*.*</Version>
        </ModuleReference>
      </Dependencies>
       <CSPApplication 
        Url="/movie-api"
        DispatchClass="dc.movie.MovieRESTApp"
        MatchRoles=":{$dbrole}"
        PasswordAuthEnabled="1"
        UnauthenticatedEnabled="0"
        Recurse="1"
        UseCookies="2"
        CookiePath="/movie-api"
       />
    </Module>
  </Document>
</Export>
```
You can see CSPApplication tag, used to run the application API in the /movie-api URI and enable or disable password to consume the API.

6. Create the LICENSE file inside movie folder to setting the license of your application. Content:

```
MIT License
Copyright (c) 2019 InterSystems Developer Community Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

7. Create the README.md file inside movie folder to document your application to the users using markdown language. Content:

```
## movie-rest-application
This is a sample of a REST API application built with ObjectScript in InterSystems IRIS.
```

8. Create .vscode folder inside movie folder. Create settings.json file inside .vscode folder to configure server connection between VSCode and your IRIS instance. Content:

```
{
    "files.associations": {
        "Dockerfile*": "dockerfile",
        "iris.script": "objectscript"
      },

    "objectscript.conn" :{
      "ns": "USER",
      "username": "_SYSTEM",
      "password": "SYS",
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      },

      "active": true
    },

    "sqltools.connections": [
      {
        "namespace": "USER",
        "connectionMethod": "Server and Port",
        "showSystem": false,
        "previewLimit": 50,
        "server": "localhost",
        "port": 52773,
        "askForPassword": false,
        "driver": "InterSystems IRIS",
        "name": "objectscript-docker",
        "username": "_SYSTEM",
        "password": "SYS"
      }
    ]
}
```
9. Create the folder src inside movie folder to put your source code folders and files.

10. Create dc folder inside src folder. This is a convention when your build projects to the InterSystems Developer Community, otherwise is not necessary.

11. Create movie folder inside dc folder. This folder will be the folder to your objectscript classes.

12. Create our first class, MovieRESTApp.cls file, inside src\dc\movie folder. This file will be the IRISRESTApplication class. Content:

````

Class dc.movie.MovieRESTApp Extends %CSP.REST
{
 
Parameter CHARSET = "utf-8";
Parameter CONVERTINPUTSTREAM = 1;
Parameter CONTENTTYPE = "application/json";
Parameter Version = "1.0.0";
Parameter HandleCorsRequest = 1;
 
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap"]
{
<Routes>

<!-- Server Info -->
<Route Url="/" Method="GET" Call="GetInfo" Cors="true"/>

<!-- Swagger specs -->
<Route Url="/_spec" Method="GET" Call="SwaggerSpec" />

</Routes>
}

 
ClassMethod %ProcessResult(pStatus As %Status = {$$$OK}, pResult As %DynamicObject = "") As %Status [ Internal ]
{

  #dim %response As %CSP.Response

  SET tSC = $$$OK

  IF $$$ISERR(pStatus) {

    SET %response.Status = 500

    SET tSC = ..StatusToJSON(pStatus, .tJSON)

    IF $isobject(tJSON) {

      SET pResult = tJSON

    } ELSE {

      SET pResult = { "errors": [ { "error": "Unknown error parsing status code" } ] }

    }

  } 

  ELSEIF pStatus=1 {

    IF '$isobject(pResult){

      SET pResult = {

      }

    }

  }

  ELSE {

    SET %response.Status = pStatus

    SET error = $PIECE(pStatus, " ", 2, *)

    SET pResult = {

      "error": (error)

    }

  }

  IF pResult.%Extends("%Library.DynamicAbstractObject") {

    WRITE pResult.%ToJSON()

  }

  ELSEIF pResult.%Extends("%JSON.Adaptor") {

    DO pResult.%JSONExport()

  }

  ELSEIF pResult.%Extends("%Stream.Object") {

    DO pResult.OutputToDevice()

  }

  QUIT tSC

}

 
ClassMethod SwaggerSpec() As %Status
{

  Set tSC = ##class(%REST.API).GetWebRESTApplication($NAMESPACE, %request.Application, .swagger)

  Do swagger.info.%Remove("x-ISC_Namespace")

  Set swagger.basePath = "/movie-api" 

  Set swagger.info.title = "Movie API"

  Set swagger.info.version = "1.0"

  Set swagger.host = "localhost:52773"

  Return ..%ProcessResult($$$OK, swagger)

}
 
}
````
Note 1: The class extends CSP.REST to be used as the REST Endpoint.

Note 2: the parameter chaset is used to encode requests and responses with UTF-8.

Note 3: the CONVERTINPUTSTREAM is used to force the request content in the UTF-8, without this you can have problems with special latin chars.

Note 4: CONTENTTYPE is used to declare the content using JSON, not XML.

Note 5: HandleCorsRequest = 1 is necessary to allows you consume the API from other servers different from the IRIS server.

Note 6: Routes are used to declare API URI to each class method.

Note 7: SwaggerSpec from CSP.REST class allows you generate the API swagger (API web documentation) content.

Now you have the following folders and files:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aur5um97ewhmsqvjd5d1.png)

13. Open VSCode Terminal (menu Terminal > New Terminal) and type:

```
docker-compose up -d --build
```
This will build the docker instance and run it.

14. Test your API with Swagger-UI. In the browser and type: http://localhost:52773/swagger-ui/index.html. Pay attention to the address bar (fix the url, if necessary to correct address)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0w0ti5aunovfpl5nuw02.png)

## Connection beetween VSCode and IRIS  
1. Click in the ObjectScript bar (in the VSCode footer)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uabr9fvhjgimjlw6bzm.png)

2. Select Toogle Connection in the Top:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rruhp6g423v8im0hoh7.png)

3. Check the connection status into ObjectScript Explorer (you will be able to see folders and classes created):
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stcugup3e5lnvi2yo4wx.png)

## Persistent Classes to the Movie Catalog Application  
In this section we will create the persistent domain classes to store and query the business data. See the DBeaver Diagram:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pb25ndbkqsta01p4963g.png)

1. Create the folder model inside src\dc\movie folder.

2. Create the Actor.cls file inside model folder. Write the content:  

````
Class dc.movie.model.Actor Extends (%Persistent, %JSON.Adaptor)
{
Parameter %JSONREFERENCE = "ID";

Property actorId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ];
Property name As %VarString(MAXLEN = 120);  
Property dob As %Date;
Property genre As %Integer(VALUELIST = ",1,2");
 
}

````
3. Create the Movie.cls file inside model folder. Write the content:

````
Class dc.movie.model.Movie Extends (%Persistent, %JSON.Adaptor)
{

Parameter %JSONREFERENCE = "ID";

Property movieId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ]; 
Property name As %VarString(MAXLEN = 120);
Property releaseDate As %Date;
Property duration As %Integer;
Property imdb As %String(MAXLEN = 300);
Property movieCategory As dc.movie.model.MovieCategory;
ForeignKey MovieCategoryFK(movieCategory) References dc.movie.model.MovieCategory();

}
````
4. Create the MovieCategory.cls file inside model folder. Write the content:

````
Class dc.movie.model.MovieCategory Extends (%Persistent, %JSON.Adaptor)
{
 
Parameter %JSONREFERENCE = "ID";

Property movieCategoryId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ]; 
Property name As %VarString(MAXLEN = 120);

}
````
5. Create the Casting.cls file inside model folder. Write the content:

````
Class dc.movie.model.Casting Extends (%Persistent, %JSON.Adaptor)
{

 
Parameter %JSONREFERENCE = "ID";
 
Property castingId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ];
Property movie As dc.movie.model.Movie;
ForeignKey MovieFK(movie) References dc.movie.model.Movie();
Property actor As dc.movie.model.Actor;
ForeignKey ActorFK(actor) References dc.movie.model.Actor();
Property characterName As %String(MAXLEN = 100);

Index CastingIndex On (movie, actor) [ Unique ];
 
}
````
See the created files:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1n22lvk121fmwn6a5bx.png)

Note 1: Parameter %JSONREFERENCE = "ID" allows return ID value inside JSON response.

Note 2: Property actorId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ] and the other similar properties are used to return class+id into JSON response.

Note 3: (VALUELIST = "1,2") set possible values to 1 or 2 only.

Note 4: ForeignKey MovieFK(movie) References dc.movie.model.Movie() and similar are used to create a SQL foreign key reference.

Note 5: Index CastingIndex On (movie, actor) [ Unique ] and similar are used to not allows duplicate values combining properties in the On (movie and actor).

Note 6: I'm using Camel Case to property names because a best practice for JSON attribute names.

## Business Service Classes to the Movie Catalog Application 
In this section we will create the classes with business logic (methods to do persistence, query and calculations).

1. Create the service folder inside src\dc\movie.

2. Create CrudUtilService.cls file inside service folder. Write the content:

````
Class dc.movie.service.CrudUtilService Extends %CSP.REST
{
 
Parameter CHARSET = "utf-8";
Parameter CONVERTINPUTSTREAM = 1;
Parameter CONTENTTYPE = "application/json";
Parameter Version = "1.0.0";
Parameter HandleCorsRequest = 1;
 
/// Return all the records
ClassMethod GetAll(DomainClass As %Persistent) As %Status
{

    #dim tSC As %Status = $$$OK
    Set rset = DomainClass.ExtentFunc()
    Set %response.ContentType = ..#CONTENTTYPEJSON
    Set %response.Headers("Access-Control-Allow-Origin")="*"

    Write "["

    if rset.%Next() {
        Set actor = DomainClass.%OpenId(rset.ID)    
        Do actor.%JSONExport()
    }

    While rset.%Next() {   
        Write "," 
        Set actor = DomainClass.%OpenId(rset.ID)    
        Do actor.%JSONExport()
    }

    Write "]"
 
    Quit tSC
}
 
/// Return one record
ClassMethod GetOne(DomainClass As %Persistent, id As %Integer) As %Status
{

    #dim tSC As %Status = $$$OK
    #dim e As %Exception.AbstractException
 
    #; Set the response header to plain text
    Set %response.ContentType = ..#CONTENTTYPEJSON
    Set %response.Headers("Access-Control-Allow-Origin")="*"
 
    Set domain = DomainClass.%OpenId(id)
 
    If '$IsObject(domain) Quit ..Http404()
 
    Do domain.%JSONExport()

    Quit tSC

}

 
/// Creates a new record
ClassMethod Create(DomainClass As %Persistent) As %Status
{

    #dim tSC As %Status = $$$OK
    #dim e As %Exception.AbstractException

    Set domain = DomainClass.%New()
    Set data = {}.%FromJSON(%request.Content)

    $$$TOE(tSC, domain.%JSONImport(data))
    $$$TOE(tSC, domain.%Save())
 
    Write domain.%JSONExport()

    Set %response.Status = 204
    Set %response.ContentType = ..#CONTENTTYPEJSON
    Set %response.Headers("Access-Control-Allow-Origin")="*"

    Quit tSC

}

 
/// Update a record with id
ClassMethod Update(DomainClass As %Persistent, id As %Integer) As %Status
{

    #dim tSC As %Status = $$$OK 
    #dim e As %Exception.AbstractException

    Set domain = DomainClass.%OpenId(id) 

    If '$IsObject(domain) Return ..Http404()

    Set data = {}.%FromJSON(%request.Content)
 
    $$$TOE(tSC, domain.%JSONImport(data))

    $$$TOE(tSC, domain.%Save())

    Write domain.%JSONExport()
 
    Set %response.Status = 200

    Set %response.ContentType = ..#CONTENTTYPEJSON

    Set %response.Headers("Access-Control-Allow-Origin")="*"
 
    Quit tSC

}

 
/// Delete a record with id
ClassMethod Delete(DomainClass As %Persistent, id As %Integer) As %Status
{

    #dim tSC As %Status = $$$OK
    #dim e As %Exception.AbstractException

    Set domain = DomainClass.%OpenId(id)

    If '$IsObject(domain) Return ..Http404()
 
    $$$TOE(tSC, domain.%DeleteId(id))
 
    Set %response.Status = 200
 
    Set %response.ContentType = ..#CONTENTTYPEJSON

    Set %response.Headers("Access-Control-Allow-Origin")="*"
 
    Quit tSC

}

}
````
3. Create MovieService.cls file inside service folder. Write the content:

````
Class dc.movie.service.MovieService Extends %CSP.REST
{
 
ClassMethod GetAll() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetAll(##class(dc.movie.model.Movie).%New())

}

 
ClassMethod GetOne(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetOne(##class(dc.movie.model.Movie).%New(), id)

}

 
ClassMethod Create() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Create(##class(dc.movie.model.Movie).%New())

}

 
ClassMethod Update(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Update(##class(dc.movie.model.Movie).%New(), id)

}
 
ClassMethod Delete(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Delete(##class(dc.movie.model.Movie).%New(), id)

}
 
/// Return casting from the movie
ClassMethod GetMovieCasting(id As %Integer) As %Status
{
 
    #dim tSC As %Status = $$$OK
 
    Set qry = "SELECT actor->name AS actorName, characterName, movie->name AS movieName FROM dc_movie_model.Casting WHERE movie = ?"

    Set tStatement = ##class(%SQL.Statement).%New()  

    Set qStatus = tStatement.%Prepare(qry)

    If tSC'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}

    Set rset = tStatement.%Execute(id)
    Set %response.ContentType = ..#CONTENTTYPEJSON
    Set %response.Headers("Access-Control-Allow-Origin")="*"

    Set result = []

    While rset.%Next() {

        Set item = {}

        Set item.actorName = rset.actorName

        Set item.movieName = rset.movieName

        Set item.characterName = rset.characterName

        Do result.%Push(item)

    }

    Write result.%ToJSON()

    Quit tSC
}

}
````
4. Create MovieCategoryService.cls file inside service folder. Write the content:

````
Class dc.movie.service.MovieCategoryService
{
 
ClassMethod GetAll() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetAll(##class(dc.movie.model.MovieCategory).%New())

}

 
ClassMethod GetOne(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetOne(##class(dc.movie.model.MovieCategory).%New(), id)

}

ClassMethod Create() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Create(##class(dc.movie.model.MovieCategory).%New())

}

 
ClassMethod Update(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Update(##class(dc.movie.model.MovieCategory).%New(), id)

}
 
ClassMethod Delete(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Delete(##class(dc.movie.model.MovieCategory).%New(), id)

}
 
}
````
5. Create ActorService.cls file inside service folder. Write the content:

````
Class dc.movie.service.ActorService
{

 
ClassMethod GetAll() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetAll(##class(dc.movie.model.Actor).%New())

}

 
ClassMethod GetOne(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetOne(##class(dc.movie.model.Actor).%New(), id)

}

 
ClassMethod Create() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Create(##class(dc.movie.model.Actor).%New())

}

 
ClassMethod Update(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Update(##class(dc.movie.model.Actor).%New(), id)

}

 
ClassMethod Delete(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Delete(##class(dc.movie.model.Actor).%New(), id)

}
 
}
````
6. Create CastingService.cls file inside service folder. Write the content:

````
Class dc.movie.service.CastingService
{

 
ClassMethod GetAll() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetAll(##class(dc.movie.model.Casting).%New())

}

 
ClassMethod GetOne(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).GetOne(##class(dc.movie.model.Casting).%New(), id)

}

 
ClassMethod Create() As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Create(##class(dc.movie.model.Casting).%New())

}

 
ClassMethod Update(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Update(##class(dc.movie.model.Casting).%New(), id)

}

 
ClassMethod Delete(id As %Integer) As %Status
{

    Return ##class(dc.movie.service.CrudUtilService).Delete(##class(dc.movie.model.Casting).%New(), id)

}

}
````
7. Update the file MovieRESTApp.cls to create paths to all new service class methods. Write the content:

````
Class dc.movie.MovieRESTApp Extends %CSP.REST
{

 
Parameter CHARSET = "utf-8";
Parameter CONVERTINPUTSTREAM = 1;
Parameter CONTENTTYPE = "application/json";
Parameter Version = "1.0.0";
Parameter HandleCorsRequest = 1;
 
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{

<Routes>

<!-- Server Info -->
<Route Url="/" Method="GET" Call="GetInfo" Cors="true"/>

<!-- Swagger specs -->
<Route Url="/_spec" Method="GET" Call="SwaggerSpec" />

 
<!-- List all movies -->
<Route Url="/movies" Method="GET" Call="GetAllMovies" />

<!-- Get a movie -->
<Route Url="/movies/:id" Method="GET" Call="GetMovie" />

<!-- Get the movie casting -->
<Route Url="/movies/casting/:id" Method="GET" Call="GetMovieCasting" />

<!-- Create new movie -->
<Route Url="/movies" Method="POST" Call="CreateMovie" />

<!-- Update a movie -->
<Route Url="/movies/:id" Method="PUT" Call="UpdateMovie" />

<!-- Delete a movie -->
<Route Url="/movies/:id" Method="DELETE" Call="DeleteMovie" />

 
<!-- List all movie categories --> 
<Route Url="/categories" Method="GET" Call="GetAllMovieCategories" />

<!-- Get a movie category -->
<Route Url="/categories/:id" Method="GET" Call="GetMovieCategory" />

<!-- Create new movie category -->
<Route Url="/categories" Method="POST" Call="CreateMovieCategory" />

<!-- Update a movie category -->
<Route Url="/categories/:id" Method="PUT" Call="UpdateMovieCategory" />

<!-- Delete a movie category -->
<Route Url="/categories/:id" Method="DELETE" Call="DeleteMovieCategory" />

 
<!-- List all actors -->
<Route Url="/actors" Method="GET" Call="GetAllActors" />

<!-- Get a actor -->
<Route Url="/actors/:id" Method="GET" Call="GetActor" />

<!-- Create new actor -->
<Route Url="/actors" Method="POST" Call="CreateActor" />

<!-- Update a actor -->
<Route Url="/actors/:id" Method="PUT" Call="UpdateActor" />

<!-- Delete a actor -->
<Route Url="/actors/:id" Method="DELETE" Call="DeleteActor" />

 
<!-- List all castings -->
<Route Url="/castings" Method="GET" Call="GetAllCastings" />

<!-- Get a actor -->
<Route Url="/castings/:id" Method="GET" Call="GetCasting" />

<!-- Create new actor -->
<Route Url="/castings" Method="POST" Call="CreateCasting" />

<!-- Update a actor -->
<Route Url="/castings/:id" Method="PUT" Call="UpdateCasting" />

<!-- Delete a actor -->
<Route Url="/castings/:id" Method="DELETE" Call="DeleteCasting" />
 
</Routes>

}

 
/// List movies
ClassMethod GetAllMovies() As %Status
{

  Return ##class(dc.movie.service.MovieService).GetAll()

}

 
/// Get movie casting
ClassMethod GetMovieCasting(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieService).GetMovieCasting(id)

}

 
/// Get a movie
ClassMethod GetMovie(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieService).GetOne(id)

}

 
// Create a new movie
ClassMethod CreateMovie() As %Status
{

  Return ##class(dc.movie.service.MovieService).Create()

}

 
// Update a movie
ClassMethod UpdateMovie(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieService).Update(id)

}

 
// Delete a movie
ClassMethod DeleteMovie(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieService).Delete(id)

}

 
/// List movies categories
ClassMethod GetAllMovieCategories() As %Status
{

  Return ##class(dc.movie.service.MovieCategoryService).GetAll()

}

 
/// Get a movie category
ClassMethod GetMovieCategory(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieCategoryService).GetOne(id)

}

 
// Create a new movie category
ClassMethod CreateMovieCategory() As %Status
{

  Return ##class(dc.movie.service.MovieCategoryService).Create()

}

 
// Update a movie category
ClassMethod UpdateMovieCategory(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieCategoryService).Update(id)

}
 
// Delete a movie category
ClassMethod DeleteMovieCategory(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.MovieCategoryService).Delete(id)

}
 
/// List actors
ClassMethod GetAllActors() As %Status
{

  Return ##class(dc.movie.service.TestActorService).GetAll()

}

/// Get an actor
ClassMethod GetActor(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.ActorService).GetOne(id)

}
 
// Create a new actor
ClassMethod CreateActor() As %Status
{

  Return ##class(dc.movie.service.ActorService).Create()

}
 
// Update an actor
ClassMethod UpdateActor(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.ActorService).Update(id)

}
 
// Delete an actor
ClassMethod DeleteActor(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.ActorService).Delete(id)

}
 
/// List castings
ClassMethod GetAllCastings() As %Status
{

  Return ##class(dc.movie.service.CastingService).GetAll()

}
 
/// Get a casting
ClassMethod GetCasting(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.CastingService).GetOne(id)

}
 
// Create a new casting item
ClassMethod CreateCasting() As %Status
{

  Return ##class(dc.movie.service.CastingService).Create()

}
 
// Update a casting
ClassMethod UpdateCasting(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.CastingService).Update(id)

}
 
// Delete a casting
ClassMethod DeleteCasting(id As %Integer) As %Status
{

  Return ##class(dc.movie.service.CastingService).Delete(id)

}

 
/// General information
ClassMethod GetInfo() As %Status
{

  SET version = ..#Version
  SET fmt=##class(%SYS.NLS.Format).%New("ptbw")

  SET info = {
    "Service": "Movie API",
    "version": (version),
    "Developer": "Yuri Gomes",
    "Status": "Ok",
    "Date": ($ZDATETIME($HOROLOG))
  }

  Set %response.ContentType = ..#CONTENTTYPEJSON
  Set %response.Headers("Access-Control-Allow-Origin")="*"

  Write info.%ToJSON()

  Quit $$$OK

}

 
ClassMethod %ProcessResult(pStatus As %Status = {$$$OK}, pResult As %DynamicObject = "") As %Status [ Internal ]
{

  #dim %response As %CSP.Response

  SET tSC = $$$OK

  IF $$$ISERR(pStatus) {
    SET %response.Status = 500
    SET tSC = ..StatusToJSON(pStatus, .tJSON)

    IF $isobject(tJSON) {
      SET pResult = tJSON

    } ELSE {

      SET pResult = { "errors": [ { "error": "Unknown error parsing status code" } ] }

    }

  } 

  ELSEIF pStatus=1 {

    IF '$isobject(pResult){
      SET pResult = {

      }

    }

  }

  ELSE {

    SET %response.Status = pStatus

    SET error = $PIECE(pStatus, " ", 2, *)

    SET pResult = {

      "error": (error)

    }

  }


  IF pResult.%Extends("%Library.DynamicAbstractObject") {
    WRITE pResult.%ToJSON()

  }

  ELSEIF pResult.%Extends("%JSON.Adaptor") {
    DO pResult.%JSONExport()

  }

  ELSEIF pResult.%Extends("%Stream.Object") {
    DO pResult.OutputToDevice()

  }

  QUIT tSC

}

 
ClassMethod SwaggerSpec() As %Status
{

  Set tSC = ##class(%REST.API).GetWebRESTApplication($NAMESPACE, %request.Application, .swagger)

  Do swagger.info.%Remove("x-ISC_Namespace")
  Set swagger.basePath = "/movie-api" 
  Set swagger.info.title = "Movie API"
  Set swagger.info.version = "1.0"
  Set swagger.host = "localhost:52773"
  Return ..%ProcessResult($$$OK, swagger)

}

}
````
8. The files and folders to the final project are:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j084teyir18qq0aq4ifs.png)

9. Test your new methods acessing http://localhost:52773/swagger-ui/index.html.

Note 1: REST paths are following business topic in plural with /id when we need pass id to the entity and camel case to paths to.

Note 2: We use verb GET to queries, POST to new records, PUT to update record and DELETE to delete a record.

Note 3: In <Route Url="/movies/casting/:id" Method="GET" Call="GetMovieCasting" /> I used /casting indicating a second purpose (get the movie and it casting). This method runs ToJSON(), because is a DynamicArray ([]) with Dynamic items ({}).

Note 4: I created the CrudUtilService class utility to do generic CRUD methods, following the Dont Repeat Yourself principle.


Enjoy this tutorial!