|
| 1 | +--- |
| 2 | +title: Your first Egg application |
| 3 | +--- |
| 4 | + |
| 5 | +It's time to get your hands dirty. In this guide, we'll walk you through every step of building a (really) simple Egg application. |
| 6 | + |
| 7 | +:::tip Tips |
| 8 | +- This guide is for beginners who want to get started with an Egg application from scratch. |
| 9 | +- Most of the plugins are included in Egg by default. You can switch them on/off in the configurations. |
| 10 | +- More information can be found in [Guide](../guide/README.md). |
| 11 | +::: |
| 12 | + |
| 13 | +## TodoMVC |
| 14 | + |
| 15 | +We'll build a [TodoMVC](http://todomvc.com/) application from scratch, using Egg. |
| 16 | + |
| 17 | +The complete version can be found at [eggjs/examples/todomvc](https://github.com/eggjs/examples/tree/master/todomvc). |
| 18 | + |
| 19 | +<!--  --> |
| 20 | + |
| 21 | +## Creating an Egg aplication |
| 22 | + |
| 23 | +### Preparation |
| 24 | + |
| 25 | +- **OS**: All operating systems are supported, but `macOS` is recommended. |
| 26 | +- **Node.js**: If you are new to Node.js, there's an [installation guide here](./prepare.md). |
| 27 | + |
| 28 | +### Scaffolding |
| 29 | + |
| 30 | +There's a [tool](../workflow/development/init.md) for scaffolding Egg applications. |
| 31 | + |
| 32 | +:::tip Tips |
| 33 | +The examples below use $ to represent the terminal prompt in a UNIX-like OS, it might be different in some occasions. |
| 34 | +::: |
| 35 | + |
| 36 | +```bash |
| 37 | +$ mkdir demo && cd demo |
| 38 | +$ npm init egg --type=simple |
| 39 | +$ npm install |
| 40 | +``` |
| 41 | + |
| 42 | +### Directory |
| 43 | + |
| 44 | +The `demo` directory will be looking like this. They are generated by following [the directory rules](../guide/directory.md) of an Egg application. We strongly recommend you to follow it when creating new folders. |
| 45 | + |
| 46 | +```bash |
| 47 | +demo |
| 48 | +├── app |
| 49 | +│ ├── controller # Controllers |
| 50 | +│ │ └── home.js |
| 51 | +│ └── router.js # Routers |
| 52 | +├── config # Configurations |
| 53 | +│ ├── config.default.js |
| 54 | +│ └── plugin.js |
| 55 | +├── test # Testing files |
| 56 | +├── README.md |
| 57 | +└── package.json |
| 58 | +``` |
| 59 | + |
| 60 | +### `Controller` |
| 61 | + |
| 62 | +[Controller](../guide/controller.md) is responsible for **parsing user requests**, **handling them** and **response back**. |
| 63 | + |
| 64 | +```js |
| 65 | +// app/controller/home.js |
| 66 | +const { Controller } = require('egg'); |
| 67 | + |
| 68 | +class HomeController extends Controller { |
| 69 | + async index() { |
| 70 | + const { ctx } = this; |
| 71 | + ctx.body = 'hi, egg'; |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +module.exports = HomeController; |
| 76 | +``` |
| 77 | + |
| 78 | +Every controller needs to mount to a certain `URL` which tells Egg when to handle incoming requests. In the case below, `/` would be handled with the controller you just wrote. |
| 79 | + |
| 80 | +```js |
| 81 | +// app/router.js |
| 82 | +/** |
| 83 | + * @param {Egg.Application} app - egg application |
| 84 | + */ |
| 85 | +module.exports = app => { |
| 86 | + const { router, controller } = app; |
| 87 | + router.get('/', controller.home.index); |
| 88 | +}; |
| 89 | +``` |
| 90 | + |
| 91 | +### Local Development |
| 92 | + |
| 93 | +Egg comes with a [tool](../workflow/development/development.md) for local development. |
| 94 | + |
| 95 | +- Getting your application up; |
| 96 | +- Watching file changes; |
| 97 | +- Generating `d.ts` files if you're using TypeScript; |
| 98 | + |
| 99 | +To start your application: |
| 100 | + |
| 101 | +```bash |
| 102 | +$ npm run dev |
| 103 | +``` |
| 104 | + |
| 105 | +Next, go to `http://127.0.0.1:7001` in your browser. |
| 106 | + |
| 107 | +### Template Rendering |
| 108 | + |
| 109 | +In most cases, we want to present web pages to users, using template rendering. But Egg doesn't come with this feature enabled by default. You have to choose a *template engine plugin* and enable it by yourself. |
| 110 | + |
| 111 | +:::tip What is plugin? |
| 112 | +The plugin system is a special feature of Egg. It makes the core application simple, yet stable and highly efficient. You can use plugins for business logic reusing and building an eco-system. |
| 113 | + |
| 114 | +Go to [Development - Plugins](../guide/plugin.md) to see more. |
| 115 | +::: |
| 116 | + |
| 117 | +In this guide, we'll be using [Nunjucks](https://mozilla.github.io/nunjucks/) to render our pages. |
| 118 | + |
| 119 | +To start, you need to install [egg-view-nunjucks] first. |
| 120 | + |
| 121 | +```bash |
| 122 | +$ npm i egg-view-nunjucks --save |
| 123 | +``` |
| 124 | + |
| 125 | +Then, enable it. |
| 126 | + |
| 127 | +```js |
| 128 | +// config/plugin.js |
| 129 | +exports.nunjucks = { |
| 130 | + enable: true, |
| 131 | + package: 'egg-view-nunjucks' |
| 132 | +}; |
| 133 | +``` |
| 134 | + |
| 135 | +Every template file should be located in `app/view` and its enclosing folders. |
| 136 | + |
| 137 | +```html |
| 138 | +<!-- app/view/home.tpl --> |
| 139 | +<html> |
| 140 | + ... |
| 141 | + <script src="/public/main.js"></script> |
| 142 | +</html> |
| 143 | +``` |
| 144 | + |
| 145 | +Lastly, change your `Controller` into this. |
| 146 | + |
| 147 | +```js |
| 148 | +class HomeController extends Controller { |
| 149 | + async index() { |
| 150 | + const { ctx } = this; |
| 151 | + // 渲染模板 `app/view/home.tpl` |
| 152 | + await ctx.render('home.tpl'); |
| 153 | + } |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +### Static Files |
| 158 | + |
| 159 | +Normally, static files are served through: |
| 160 | + |
| 161 | +- Content Deliver Network (recommend); |
| 162 | +- Current application; |
| 163 | + |
| 164 | +Egg comes with a handy plugin [egg-static] for serving static files in current application. |
| 165 | + |
| 166 | +egg-static would mount the folder `app/public` to the route `/public` by default. |
| 167 | + |
| 168 | +:::warning Warning |
| 169 | +- Files served by `egg-static` will be responsed with a `maxAge` header which indicates the browsers to cache for a year by default. |
| 170 | +- [CSRF mechanism](../ecosystem/security/csrf.md) is open by default. `AJAX` requests need to have a valid `token` so that they can be handled properly. If you are using axios, here's an example: |
| 171 | + |
| 172 | +```js |
| 173 | +// app/public/main.js |
| 174 | +axios.defaults.headers.common['x-csrf-token'] = Cookies.get('csrfToken'); |
| 175 | +``` |
| 176 | +::: |
| 177 | + |
| 178 | +### Configurations |
| 179 | + |
| 180 | +It's quite common for developers to manage a bunch of [configurations](../guide/config.md). Egg's configuration feature would definately be helpful. |
| 181 | + |
| 182 | +For example, to use [egg-view-nunjucks], you will have to add a few lines to `config/config.default.js`. |
| 183 | + |
| 184 | +```js |
| 185 | +// config/config.default.js |
| 186 | +config.view = { |
| 187 | + defaultViewEngine: 'nunjucks', |
| 188 | + mapping: { |
| 189 | + '.tpl': 'nunjucks', |
| 190 | + '.html': 'nunjucks', |
| 191 | + }, |
| 192 | +}; |
| 193 | +``` |
| 194 | + |
| 195 | +:::warning Warning |
| 196 | +It's `./config`, not `./app/config`! |
| 197 | +::: |
| 198 | + |
| 199 | +### `Service` |
| 200 | + |
| 201 | +Normally, business logics are in the [Service](../guide/service.md). They can be called by the `Controller`. |
| 202 | + |
| 203 | +Here is an example for creating a todo item. |
| 204 | + |
| 205 | +```js |
| 206 | +// app/service/todo.js |
| 207 | +const { Service } = require('egg'); |
| 208 | + |
| 209 | +class TodoService extends Service { |
| 210 | + /** |
| 211 | + * create todo |
| 212 | + * @param {Todo} todo - todo info without `id`, but `title` required |
| 213 | + */ |
| 214 | + async create(todo) { |
| 215 | + // validate |
| 216 | + if (!todo.title) this.ctx.throw(422, 'task title required'); |
| 217 | + |
| 218 | + // normalize |
| 219 | + todo.id = Date.now().toString(); |
| 220 | + todo.completed = false; |
| 221 | + |
| 222 | + this.store.push(todo); |
| 223 | + return todo; |
| 224 | + } |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +Then, call it in `Controller`. |
| 229 | + |
| 230 | +```js |
| 231 | +// app/controller/todo.js |
| 232 | +class TodoController extends Controller { |
| 233 | + async create() { |
| 234 | + const { ctx, service } = this; |
| 235 | + |
| 236 | + // params validate, need `egg-validate` plugin |
| 237 | + // ctx.validate({ title: { type: 'string' } }); |
| 238 | + |
| 239 | + ctx.status = 201; |
| 240 | + ctx.body = await service.todo.create(ctx.request.body); |
| 241 | + } |
| 242 | +} |
| 243 | +``` |
| 244 | + |
| 245 | +### RESTful |
| 246 | + |
| 247 | +Egg has [built-in support](../guide/router.md#RESTful-风格的-URL-定义) for RESTful routing. |
| 248 | + |
| 249 | +```js |
| 250 | +// app/router.js |
| 251 | +module.exports = app => { |
| 252 | + const { router, controller } = app; |
| 253 | + |
| 254 | + // RESTful mapping |
| 255 | + router.resources('/api/todo', controller.todo); |
| 256 | +}; |
| 257 | +``` |
| 258 | + |
| 259 | +The controller `controller.todo` must implement these functions: |
| 260 | + |
| 261 | +```js |
| 262 | +// app/controller/todo.js |
| 263 | +class TodoController extends Controller { |
| 264 | + // `GET /api/todo` |
| 265 | + async index() {} |
| 266 | + |
| 267 | + // `POST /api/todo` |
| 268 | + async create() {} |
| 269 | + |
| 270 | + // `PUT /api/todo` |
| 271 | + async update() {} |
| 272 | + |
| 273 | + // `DELETE /api/todo` |
| 274 | + async destroy() {} |
| 275 | +} |
| 276 | +``` |
| 277 | + |
| 278 | +### Unit Testing |
| 279 | + |
| 280 | +It is highly recommended to do the unit testing. Egg comes with [a tool](../workflow/development/unittest.md) for you to test your application. |
| 281 | + |
| 282 | +```js |
| 283 | +// test/app/controller/todo.test.js |
| 284 | +const { app, mock, assert } = require('egg-mock/bootstrap'); |
| 285 | + |
| 286 | +describe('test/app/controller/todo.test.js', () => { |
| 287 | + it('should add todo', () => { |
| 288 | + return app.httpRequest() |
| 289 | + .post('/api/todo') |
| 290 | + .send({ title: 'Add one' }) |
| 291 | + .expect('Content-Type', /json/) |
| 292 | + .expect('X-Response-Time', /\d+ms/) |
| 293 | + .expect(201) |
| 294 | + .expect(res => { |
| 295 | + assert(res.body.id); |
| 296 | + assert(res.body.title === 'Add one'); |
| 297 | + assert(res.body.completed === false); |
| 298 | + }); |
| 299 | + }); |
| 300 | +}); |
| 301 | +``` |
| 302 | + |
| 303 | +[Node.js]: http://nodejs.org |
| 304 | +[egg-static]: https://github.com/eggjs/egg-static |
| 305 | +[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks |
| 306 | +[Nunjucks]: https://mozilla.github.io/nunjucks/ |
0 commit comments