
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
"Stop writing endpoints"
Typescript RESTful Server Architecture
Created and maintained by Stateless Studio
PointyAPI is a library for quickly creating robust API servers.
request.user make authorization a breezeCanRead(), and CanWrite() fields to ensure the user can read/write specific fieldsModels are created as Typescript classes. Here's an example class for a basic user:
@Entity()
class User extends BaseUser
{
// User ID
@PrimaryGeneratedColumn() // Primary column
@IsInt() // Validation - Value must be integer
@BodyguardKey() // Authentication - User must match this to be considered "self"
@AnyoneCanRead() // Authorization - Anyone is allowed to read this field
public id: number = undefined;
// Username
@Column({ unique: true }) // Database column - Usernames are unique
@IsAlphanumeric() // Validation - must be alphanumeric
@Length(4, 16) // Validation - must be between 4-16 characters
@AnyoneCanRead() // Authentication - Anyone has read privelege to this member
@OnlySelfCanWrite() // Authorization - Only self can write this member
public username: string = undefined;
// Password
@Column() // Database column
@OnlySelfCanWrite() // Authentication - Only self can write this member
// No read key - nobody can read this field
public password: string = undefined;
/**
* hashPassword
*/
public hashPassword() {
if (this.password) {
this.password = hashSync(this.password, 12);
}
}
/**
* Hash the user's password before post
*/
public async beforePost(request: Request, response: Response) {
this.hashPassword();
return true;
}
/**
* Hash the user's password on update
*/
public async beforePatch(request: Request, response: Response) {
this.hashPassword();
return true;
}
}
You can check out more sample models in examples/, or read more about them in the Models documentation.
Routes are Express routes which chain together PointyAPI middleware functions. Here's an example User router:
const router: Router = Router();
/**
* Load model into PointyAPI
*/
async function loader(request, response, next) {
if (await setModel(request, response, User)) {
next();
}
}
// POST - Load model, filter members, and save
router.post('/', loader, postFilter, postEndpoint);
// GET - Load model, filter members, and send
router.get('/', loader, getFilter, getEndpoint);
// PATCH - Load model, check if user owns, filter members, and save
router.patch(`/:id`, loader, onlySelf, patchFilter, patchEndpoint);
// DELETE - Load model, check if user owns, and delete
router.delete(`/:id`, loader, onlySelf, deleteEndpoint);
export const userRouter: Router = router;
npm i -g ts-nodeCreate a folder for your project, and run these commands:
cd my-project
npm init -y
npm i pointyapi
Create a source directory, src
Inside src/, create a file index.ts
// src/index.ts
import { pointy, bootstrap, log } from 'pointyapi';
import { basicCors, loadUser } from 'pointyapi/middleware';
// Routes
// TODO: We will import routes here
// Setup
pointy.before = async (app) => {
// CORS
app.use(basicCors);
// Load user
app.use(loadUser);
// Routes
// TODO: We will set our routes here
// Database
await pointy.db
.setEntities(
[
/* TODO: We will set our models here */
ExampleUser
]
)
.connect()
.catch((error) => pointy.error(error));
};
// Start the server!
bootstrap(async () => await pointy.start());
Create a user route
By default, PointyAPI will use ExampleUser as the user model. Let's create a route for this model, so that we can access this model through our API:
src/routes/src/routes/user.ts// src/routes/user.ts
import { Router } from 'express';
import { setModel } from 'pointyapi';
import { ExampleUser } from 'pointyapi/models';
import { postFilter, getFilter, patchFilter } from 'pointyapi/filters';
import { onlySelf } from 'pointyapi/guards';
import {
getEndpoint,
postEndpoint,
patchEndpoint,
deleteEndpoint
} from 'pointyapi/endpoints';
const router: Router = Router();
// Set the route model to ExampleUser
async function loader(request, response, next) {
if (await setModel(request, response, ExampleUser)) {
next();
}
}
// Route endpoints
router.get('/', loader, getFilter, getEndpoint);
router.post('/', loader, postFilter, postEndpoint);
router.patch(`/:id`, loader, onlySelf, patchFilter, patchEndpoint);
router.delete(`/:id`, loader, onlySelf, deleteEndpoint);
export const userRouter: Router = router;
Import user route into server
Open src/index.ts up again, and let's import our new User route.
import { ExampleUser } from 'pointyapi/models'; // Add import to our user model
...
// Routes
// TODO: We will import routes here
import { userRouter } from './routes/user'; // Add import to our user route
...
// Routes
// TODO: We will set our routes here
app.use('/api/v1/user', userRouter); // Add our user route to the app
// Database
await pointy.db
.setEntities(
[
/* TODO: We will set our models here */
ExampleUser // Add our BaseModel model to the database
]
)
...
Setup package.json
Add a start script to package.json:
"scripts": {
"start": "ts-node src/index.ts"
}
Setup database
Create a database, create a .env file in the root folder of your app, and replace the values:
POINTYAPI_DB_NAME=pointyapi
POINTYAPI_DB_TYPE=postgres
POINTYAPI_DB_HOST=localhost
POINTYAPI_DB_PORT=5432
POINTYAPI_DB_USER=pointyapi
POINTYAPI_DB_PASS=password1234
Start & Test
Our server is ready to run:
npm start
Open Postman, and send a GET request for all users. You'll see the result as an empty array, as there are no users yet:
Create a user:
We'll send a POST request to /api/v1/user, with the JSON body of our new user
Let's get all users again:
You can see that now a get request for all users produces an array of our one user.
Authentication
So we can get and post users, but what if we try to delete or update a user? Let's try it:
So the server responded with 401 Unauthorized, and a body of "not self". What gives?
Open at our user router (/src/routes/user.ts). Look at our DELETE route:
router.delete(`/:id`, loader, onlySelf, deleteEndpoint);
Notice onlySelf - that means only the user can access this route. We're not logged in yet, so we're certainly not "self"
Create another router file, src/routes/auth.ts. This will be our authentication route so that we can log-in.
// src/routes/auth.ts
import { Router } from 'express';
import { loginEndpoint, logoutEndpoint } from 'pointyapi/endpoints';
import { ExampleUser } from 'pointyapi/models';
import { setModel } from 'pointyapi';
const router: Router = Router();
// Set our route model & activate auth route
async function loader(request, response, next) {
if (await setModel(request, response, ExampleUser, true)) {
next();
}
}
// Router endpoints
router.post('/', loader, loginEndpoint);
router.delete('/', loader, logoutEndpoint);
export const authRouter: Router = router;
And import this into our index.ts:
...
// Routes
// TODO: We will import routes here
import { userRouter } from './routes/user';
import { authRouter } from './routes/auth'; // Add import to our auth route
...
// Routes
// TODO: We will set our routes here
app.use('/api/v1/user', userRouter);
app.use('/api/v1/auth', authRouter); // Add our user route to the app
// Database
await pointy.db
.setEntities(
[
/* TODO: We will set our models here */
ExampleUser
]
)
...
Restart the server (ctrl+c to stop the server)
We can login with Postman:
We received a "token" back (yours will be different):
"token": "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBaQ0k2TVN3aWFXRjBJam94TlRVeE1qa3dOREkxTENKbGVIQWlPakUxTlRFek1EUTRNalY5Lk40UWNJV1hPYWVzWW9KOWh4VGx1X182dnhNVndpbkFXNGhRY2JWNkRKSUE="
We can now use that token to delete our user:
Notice that now we get a 204 No Content (which means deleted successfully!).
What about the refreshToken?
You may notice you got two tokens back: token and refreshToken.
The token is short-lived - it only lasts 15 minutes or so.
The refreshToken is long-living - it lasts about a week.
You can use the refreshToken to issue a new accessToken when it expires.
Production
To launch in production mode, please make sure the following variables are set (environment variables/.env)
It is a security risk to use auto-incremented IDs, and you should instead use UUID for all ID columns.
To switch to using UUIDs:
pgcrypto extensionuuidExtension: 'pgcrypto' in orm-cli.jsPrimaryGeneratedColumn() to PrimaryGeneratedColumn('uuid')public id: number members to public id: stringIsInt() from all ID fields, if it existsExample:
// User ID
@PrimaryGeneratedColumn('uuid') // Primary column
@BodyguardKey() // Authentication - User must match this to be considered "self"
@AnyoneCanRead() // Authorization - Anyone is allowed to read this field
public id: string = undefined;
PointyAPI uses ts-tiny-logger.
import { log } from 'pointyapi';
log.fatal('Oh no!', { someData: 'foo' });
log.error('Error!', new Error());
log.warn('Warning!', 1234);
log.info('Information!', 'Hello!');
log.debug('Debugging...', { someData: 'bar' });
import { setLog } from 'pointyapi/log';
import { Log } from 'ts-tiny-log';
import { LogLevel } from 'ts-tiny-log/levels';
setLog(new Log({
level: LogLevel.info,
}));
Read more about PointyAPI by cloning the repository and building the docs:
git clone https://github.com/StatelessStudio/pointyapi
cd pointyapi
npm install --ignore-scripts
npm i -g typedoc
npm run docs
Now open docs/index.html in your web browser.
You can also check out the examples in test/examples and even check out the test specs in test/specs
Read more about TypeORM: https://github.com/typeorm/typeorm
Read more about Class Validator: https://github.com/typestack/class-validator
Read more about Express: https://www.express.com/
Read the security checklist: https://github.com/shieldfy/API-Security-Checklist
FAQs
*"Stop writing endpoints"*
The npm package pointyapi receives a total of 13 weekly downloads. As such, pointyapi popularity was classified as not popular.
We found that pointyapi demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.