conduitcommand line tool by running the following command in your shell:
heroesby entering the following in your shell:
heroesproject directory with a default server implementation populating it.
http://localhost:8888to fetch and manipulate hero data. The application you will build in this tutorial respond to those requests.
GET /heroesto the list of heroes
GET /heroes/:idto get an individual hero
GET /heroesis called an operation. It is the combination of the HTTP method and the path of the request. Each operation is unique to an application, so your code is segmented into units for each operation. Sections with a colon, like the ':id' segment, are variable: they can be 1, 2, 3, and so on.
Routerthat makes sure the request path is
HeroesControllersthat responds with hero objects
ApplicationChannelthat you override methods in to set up your controllers. This type is already declared in
lib/channel.dart- open this file and find
entryPointcontroller is the first to handle it. In our case, this is a
Router- a subclass of
Controller. There are some controller subclasses already in Conduit for common behaviors.
routemethod on a router to attach a controller to a route. A route is a string syntax that matches the path of a request. In our current implementation, the route will match every request with the path
/example. When that request is received, a linked function runs and returns a 200 OK response with an example JSON object body.
/heroesto a controller of our own, so we can control what happens. Let's create a
HeroesController. Create a new file in
lib/controller/heroes_controller.dartand add the following code (you will need to create the subdirectory
HeroesControlleris a subclass of
Controller; this is what makes it a controller object. It overrides its
handlemethod by returning a
Responseobject. This response object has a 200 OK status code, and it body contains a JSON-encoded list of hero objects. When a controller returns a
Responseobject from its
handlemethod, it is sent to the client.
HeroesControllerisn't hooked up to the application channel. We need to link it to the router. First, import our new file at the top of
Routerfor the path
http://conduit-tutorial.conduit.dart.io. It will make a request to
http://localhost:8888/heroesand your application will serve it. You'll see your heroes in your web browser:
Routerwill send a 404 Not Found response for any request. Adding a route to a
Routercreates an entry point to a new channel that controllers can be linked to. In our application,
HeroesControlleris linked to the route
HeroesController, always send a response. They implement the behavior that a request is seeking. Middleware controllers, like
Router, handles requests before they reach an endpoint controller. A router, for example, handles a request by directing it to the right controller. Controllers like
Authorizerverify the authorization of the request. You can create all kinds of controllers to provide any behavior you like.
Routerallows for many. For example, a larger application might look like this:
Controller, giving them the ability to be linked together to handle requests. A request goes through controllers in the order they are linked. A request for the path
/userswill go through an
Authorizerand finally a
UsersController. Each of these controllers has an opportunity to respond, preventing the next controller from receiving the request.
GET /heroesrequests. The browser application uses the this list to populate its hero dashboard. If we click on an individual hero, the browser application will display an individual hero. When navigating to this page, the browser application makes a request to our server for an individual hero. This request contains the unique id of the selected hero in the path, e.g.
/heroes. Since a request for individual heroes will have a path that changes depending on the hero, we need our route to include a path variable.
:). For example, the route
/heroes/:idcontains a path variable named
id. If the request path is
/heroes/2, and so on, the request will be sent to our
HeroesControllerwill have access to the value of the path variable to determine which hero to return.
/heroes/:idno longer matches the path
/heroes. It'd be a lot easier to organize our code if both
/heroes/:idwent to our
HeroesController; it does heroic stuff. For this reason, we can declare the
:idportion of our route to be optional by wrapping it in square brackets. In
channel.dart, modify the
/heroesstill matches the route. If the path contains a second segment, the value of that segment is bound to the path variable named
id. We can access path variables through the
conduit serveagain. In the browser application, click on a hero and you will be taken to a detail page for that hero.
curl -X GET http://localhost:8888/heroes/11to view the single hero object. You can also trigger a 404 Not Found response by getting a hero that doesn't exist.
HeroesControlleris OK right now, but it'll soon run into a problem: what happens when we want to create a new hero? Or update an existing hero's name? Our
handlemethod will start to get unmanageable, quickly.
ResourceControllercomes in. A
ResourceControllerallows you to create a distinct method for each operation that we can perform on our heroes. One method will handle getting a list of heroes, another will handle getting a single hero, and so on. Each method has an annotation that identifies the HTTP method and path variables the request must have to trigger it.
HeroesControllerwith the following:
ResourceControllerimplements this method to call one of our operation methods. An operation method - like
getHeroByID- must have an
Operationannotation. The named constructor
Operation.getmeans these methods get called when the request's method is GET. An operation method must also return a
getHeroByID's annotation also has an argument - the name of our path variable
id. If that path variable exists in the request's path,
getHeroByIDwill be called. If it doesn't exist,
getAllHeroeswill be called.
conduit serveand then run
conduit serveagain. The browser application should still behave the same.
curl, you can create a SwaggerUI browser application that executes requests against your locally running application. In your project directory, run
conduit document clientand it will generate a file named
client.html. Open this file in your browser for a UI that constructs and executes requests that your application supports.
getHeroByIDmethod, we make a dangerous assumption that the path variable 'id' can be parsed into an integer. If 'id' were something else, like a string,
int.parsewould throw an exception. When exceptions are thrown in operation methods, the controller catches it and sends a 500 Server Error response. 500s are bad, they don't tell the client what's wrong. A 404 Not Found is a better response here, but writing the code to catch that exception and create this response is cumbersome.
idwill be parsed as an integer and be available to this method in the
@Bindannotation on an operation method parameter tells Conduit the value from the request we want bound. Using the named constructor
Bind.pathbinds a path variable, and the name of that variable is indicated in the argument to this constructor.
@Bind.path('id') int heroID. Only the argument to
Bind's constructor must match the actual name of the path variable. This is valuable for other types of bindings, like headers, that may contain characters that aren't valid Dart variable names, e.g.