or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions.mdapplication-lifecycle.mdcli.mdconfiguration.mdevents.mdhooks.mdindex.mdrouting.md

actions.mddocs/

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.