0
# Actions System
1
2
Sails.js uses a flexible action system for handling HTTP requests and business logic. Actions can be traditional controller methods or standalone machine-compatible functions. The actions system provides registration, management, and discovery capabilities with support for input validation, output formatting, and middleware integration.
3
4
## Action Management
5
6
### registerAction()
7
8
Register an action with Sails for route binding and execution:
9
10
```javascript { .api }
11
sails.registerAction(action: Function|Dictionary, identity: String, force?: Boolean): void
12
```
13
14
**Parameters**:
15
- `action` (Function|Dictionary) - The action function or machine definition
16
- `identity` (String) - Unique action identifier (e.g., 'user/find', 'account/login')
17
- `force` (Boolean, optional) - Overwrite existing action if it exists (default: false)
18
19
**Throws**:
20
- `E_CONFLICT` - Action already exists and `force` is not true
21
- `E_INVALID` - Action definition is invalid
22
23
**Examples**:
24
```javascript
25
// Register a simple function action
26
sails.registerAction((req, res) => {
27
return res.json({ message: 'Hello from action!' });
28
}, 'hello/world');
29
30
// Register a machine-compatible action
31
sails.registerAction({
32
friendlyName: 'Create user',
33
description: 'Create a new user account',
34
35
inputs: {
36
email: {
37
type: 'string',
38
required: true,
39
isEmail: true
40
},
41
password: {
42
type: 'string',
43
required: true,
44
minLength: 6
45
},
46
fullName: {
47
type: 'string',
48
required: true
49
}
50
},
51
52
exits: {
53
success: {
54
description: 'User created successfully'
55
},
56
emailAlreadyInUse: {
57
statusCode: 409,
58
description: 'Email address already in use'
59
}
60
},
61
62
fn: async function(inputs, exits) {
63
try {
64
// Check if email exists
65
const existingUser = await User.findOne({ email: inputs.email });
66
if (existingUser) {
67
return exits.emailAlreadyInUse();
68
}
69
70
// Create new user
71
const newUser = await User.create({
72
email: inputs.email,
73
password: await sails.helpers.passwords.hashPassword(inputs.password),
74
fullName: inputs.fullName
75
}).fetch();
76
77
return exits.success({
78
user: newUser,
79
message: 'User created successfully'
80
});
81
82
} catch (err) {
83
return exits.serverError(err);
84
}
85
}
86
}, 'user/create');
87
88
// Force overwrite existing action
89
sails.registerAction(newImplementation, 'user/create', true);
90
```
91
92
### getActions()
93
94
Retrieve all registered actions:
95
96
```javascript { .api }
97
sails.getActions(): Dictionary
98
```
99
100
**Returns**: `Dictionary` - Shallow clone of the internal actions registry
101
102
**Example**:
103
```javascript
104
const actions = sails.getActions();
105
106
console.log('Registered actions:');
107
Object.keys(actions).forEach(identity => {
108
console.log(`- ${identity}`);
109
});
110
111
// Access specific action
112
const userCreateAction = actions['user/create'];
113
if (userCreateAction) {
114
console.log('User create action found');
115
}
116
```
117
118
### reloadActions()
119
120
Reload all registered actions from their source files:
121
122
```javascript { .api }
123
sails.reloadActions(cb?: Function): void
124
```
125
126
**Parameters**:
127
- `cb` (Function, optional) - Node-style callback `(err) => {}`
128
129
**Example**:
130
```javascript
131
// Reload all actions (useful during development)
132
sails.reloadActions((err) => {
133
if (err) {
134
console.error('Failed to reload actions:', err);
135
} else {
136
console.log('Actions reloaded successfully');
137
}
138
});
139
140
// Promise-style using util.promisify
141
const { promisify } = require('util');
142
const reloadActionsAsync = promisify(sails.reloadActions.bind(sails));
143
144
try {
145
await reloadActionsAsync();
146
console.log('Actions reloaded');
147
} catch (err) {
148
console.error('Reload failed:', err);
149
}
150
```
151
152
## Action Middleware Management
153
154
### registerActionMiddleware()
155
156
Register middleware specifically for actions:
157
158
```javascript { .api }
159
sails.registerActionMiddleware(actionMiddleware: Function|Dictionary, identity: String, force?: Boolean): void
160
```
161
162
**Parameters**:
163
- `actionMiddleware` (Function|Dictionary) - Middleware function or definition
164
- `identity` (String) - Unique middleware identifier
165
- `force` (Boolean, optional) - Overwrite existing middleware
166
167
**Example**:
168
```javascript
169
// Register authentication middleware
170
sails.registerActionMiddleware(
171
(req, res, proceed) => {
172
if (!req.session.userId) {
173
return res.status(401).json({ error: 'Authentication required' });
174
}
175
return proceed();
176
},
177
'auth/require-login'
178
);
179
180
// Register validation middleware
181
sails.registerActionMiddleware({
182
friendlyName: 'Validate API key',
183
184
fn: function(req, res, proceed) {
185
const apiKey = req.headers['x-api-key'];
186
if (!apiKey || !isValidApiKey(apiKey)) {
187
return res.status(403).json({ error: 'Invalid API key' });
188
}
189
return proceed();
190
}
191
}, 'auth/validate-api-key');
192
```
193
194
## Action Formats
195
196
### Function Actions
197
198
Simple function-based actions for basic request handling:
199
200
```javascript
201
// Basic function action
202
function simpleAction(req, res) {
203
const { id } = req.params;
204
return res.json({
205
message: 'Simple action response',
206
id: id
207
});
208
}
209
210
sails.registerAction(simpleAction, 'simple/action');
211
212
// Async function action
213
async function asyncAction(req, res) {
214
try {
215
const data = await fetchDataFromDatabase();
216
return res.json(data);
217
} catch (err) {
218
return res.serverError(err);
219
}
220
}
221
222
sails.registerAction(asyncAction, 'async/action');
223
```
224
225
### Machine Actions
226
227
Machine-compatible actions with input validation and structured exits:
228
229
```javascript
230
const machineAction = {
231
friendlyName: 'Find users',
232
description: 'Find users with optional filtering',
233
234
inputs: {
235
limit: {
236
type: 'number',
237
defaultsTo: 10,
238
min: 1,
239
max: 100
240
},
241
skip: {
242
type: 'number',
243
defaultsTo: 0,
244
min: 0
245
},
246
search: {
247
type: 'string',
248
description: 'Search term for user names or emails'
249
}
250
},
251
252
exits: {
253
success: {
254
description: 'Users found successfully',
255
outputType: {
256
users: ['ref'],
257
totalCount: 'number'
258
}
259
}
260
},
261
262
fn: async function(inputs, exits) {
263
let criteria = {};
264
265
if (inputs.search) {
266
criteria.or = [
267
{ fullName: { contains: inputs.search } },
268
{ email: { contains: inputs.search } }
269
];
270
}
271
272
const users = await User.find(criteria)
273
.limit(inputs.limit)
274
.skip(inputs.skip);
275
276
const totalCount = await User.count(criteria);
277
278
return exits.success({
279
users: users,
280
totalCount: totalCount
281
});
282
}
283
};
284
285
sails.registerAction(machineAction, 'user/find');
286
```
287
288
## Route Discovery and URL Generation
289
290
### getRouteFor()
291
292
Find the first explicit route that matches a given action target:
293
294
```javascript { .api }
295
sails.getRouteFor(routeQuery: String|Dictionary): {url: String, method: String}
296
```
297
298
**Parameters**:
299
- `routeQuery` (String|Dictionary) - Target action or route query
300
301
**Returns**: Object with `url` and `method` properties
302
303
**Throws**:
304
- `E_NOT_FOUND` - No matching route found
305
- `E_USAGE` - Invalid route query
306
307
**Examples**:
308
```javascript
309
// Find route by controller method
310
try {
311
const route = sails.getRouteFor('UserController.find');
312
console.log(`Route: ${route.method.toUpperCase()} ${route.url}`);
313
// Output: Route: GET /api/users
314
} catch (err) {
315
if (err.code === 'E_NOT_FOUND') {
316
console.log('Route not found');
317
}
318
}
319
320
// Find route by action identity
321
const loginRoute = sails.getRouteFor('entrance/login');
322
console.log(loginRoute); // { url: '/login', method: 'get' }
323
324
// Find route by object query
325
const userShowRoute = sails.getRouteFor({
326
target: 'UserController.findOne'
327
});
328
console.log(userShowRoute); // { url: '/api/users/:id', method: 'get' }
329
```
330
331
### getUrlFor()
332
333
Get URL pattern for a route target:
334
335
```javascript { .api }
336
sails.getUrlFor(routeQuery: String|Dictionary): String
337
```
338
339
**Parameters**:
340
- `routeQuery` (String|Dictionary) - Target action or route query
341
342
**Returns**: `String` - URL pattern for the route
343
344
**Examples**:
345
```javascript
346
// Get URL pattern for action
347
const userCreateUrl = sails.getUrlFor('user/create');
348
console.log(userCreateUrl); // '/api/users'
349
350
// Get URL with parameters
351
const userShowUrl = sails.getUrlFor('UserController.findOne');
352
console.log(userShowUrl); // '/api/users/:id'
353
354
// Build actual URL with parameters
355
const actualUrl = userShowUrl.replace(':id', '123');
356
console.log(actualUrl); // '/api/users/123'
357
```
358
359
## Request Simulation
360
361
### request()
362
363
Create virtual requests for testing actions without HTTP server:
364
365
```javascript { .api }
366
sails.request(opts: {url: String, method?: String, params?: Dictionary, headers?: Dictionary}, cb?: Function): MockClientResponse
367
sails.request(address: String, params?: Dictionary, cb?: Function): MockClientResponse
368
```
369
370
**Parameters**:
371
- `opts` (Dictionary) - Request configuration object
372
- `url` (String) - Target URL/route
373
- `method` (String, optional) - HTTP method (default: 'GET')
374
- `params` (Dictionary, optional) - Request parameters/body
375
- `headers` (Dictionary, optional) - Request headers
376
- `address` (String) - Simple URL/route address
377
- `params` (Dictionary, optional) - Request parameters
378
- `cb` (Function, optional) - Response callback
379
380
**Returns**: `MockClientResponse` - Stream-like response object
381
382
**Examples**:
383
```javascript
384
// Basic virtual request
385
sails.request('/api/users', (err, res, body) => {
386
if (err) throw err;
387
console.log('Response status:', res.statusCode);
388
console.log('Response body:', body);
389
});
390
391
// Request with parameters
392
sails.request({
393
url: '/api/users',
394
method: 'POST',
395
params: {
396
email: 'test@example.com',
397
fullName: 'Test User',
398
password: 'password123'
399
}
400
}, (err, res, body) => {
401
console.log('User created:', body);
402
});
403
404
// Request with headers
405
sails.request({
406
url: '/api/protected',
407
headers: {
408
'Authorization': 'Bearer token123',
409
'Content-Type': 'application/json'
410
}
411
}, (err, res, body) => {
412
console.log('Protected resource:', body);
413
});
414
415
// Promise-style virtual request
416
const response = await new Promise((resolve, reject) => {
417
sails.request('/api/users/123', (err, res, body) => {
418
if (err) return reject(err);
419
resolve({ res, body });
420
});
421
});
422
```
423
424
## Action Location and Discovery
425
426
Actions are typically located in:
427
428
### Controllers Directory
429
```
430
api/controllers/
431
├── UserController.js
432
├── AuthController.js
433
└── AdminController.js
434
```
435
436
### Actions Directory
437
```
438
api/actions/
439
├── user/
440
│ ├── create.js
441
│ ├── find.js
442
│ └── update.js
443
├── auth/
444
│ ├── login.js
445
│ └── logout.js
446
└── admin/
447
└── dashboard.js
448
```
449
450
### Action File Examples
451
452
**Controller-based action** (`api/controllers/UserController.js`):
453
```javascript
454
module.exports = {
455
find: async function(req, res) {
456
const users = await User.find();
457
return res.json(users);
458
},
459
460
create: async function(req, res) {
461
const newUser = await User.create(req.allParams()).fetch();
462
return res.json(newUser);
463
}
464
};
465
```
466
467
**Standalone action** (`api/actions/user/create.js`):
468
```javascript
469
module.exports = {
470
friendlyName: 'Create user',
471
description: 'Create a new user account',
472
473
inputs: {
474
email: { type: 'string', required: true },
475
fullName: { type: 'string', required: true }
476
},
477
478
fn: async function(inputs) {
479
const user = await User.create(inputs).fetch();
480
return { user };
481
}
482
};
483
```
484
485
## Action Execution Context
486
487
Actions receive different parameters based on their format:
488
489
### Function Actions
490
```javascript
491
function actionHandler(req, res) {
492
// req - HTTP request object
493
// res - HTTP response object
494
// Direct access to Sails globals (User, sails, etc.)
495
}
496
```
497
498
### Machine Actions
499
```javascript
500
{
501
fn: async function(inputs, exits) {
502
// inputs - Validated input parameters
503
// exits - Exit functions (success, error, etc.)
504
// Access to this.req and this.res for HTTP context
505
}
506
}
507
```
508
509
## Error Handling
510
511
Actions support comprehensive error handling:
512
513
```javascript
514
// Function action error handling
515
sails.registerAction(async (req, res) => {
516
try {
517
const result = await riskyOperation();
518
return res.json(result);
519
} catch (err) {
520
if (err.code === 'E_NOT_FOUND') {
521
return res.notFound();
522
}
523
return res.serverError(err);
524
}
525
}, 'risky/operation');
526
527
// Machine action error handling
528
sails.registerAction({
529
fn: async function(inputs, exits) {
530
try {
531
const result = await performOperation(inputs);
532
return exits.success(result);
533
} catch (err) {
534
if (err.name === 'ValidationError') {
535
return exits.invalid(err.details);
536
}
537
throw err; // Will be caught by framework
538
}
539
},
540
541
exits: {
542
invalid: {
543
statusCode: 400,
544
description: 'Invalid input provided'
545
}
546
}
547
}, 'validated/operation');
548
```
549
550
## Development and Testing
551
552
### Hot Reloading
553
Actions can be reloaded during development:
554
555
```javascript
556
// Watch for file changes and reload actions
557
if (sails.config.environment === 'development') {
558
const chokidar = require('chokidar');
559
560
chokidar.watch('api/actions/**/*.js').on('change', () => {
561
sails.reloadActions((err) => {
562
if (!err) console.log('Actions reloaded');
563
});
564
});
565
}
566
```
567
568
### Testing Actions
569
```javascript
570
// Test action directly
571
describe('User Actions', () => {
572
it('should create user', (done) => {
573
sails.request({
574
url: '/api/users',
575
method: 'POST',
576
params: {
577
email: 'test@example.com',
578
fullName: 'Test User'
579
}
580
}, (err, res, body) => {
581
expect(res.statusCode).to.equal(201);
582
expect(body.user).to.exist;
583
done();
584
});
585
});
586
});
587
```
588
589
The Sails actions system provides a flexible foundation for building scalable web applications with comprehensive input validation, error handling, and testing capabilities.